def implicit_real(fn, x_min, x_max, epsilon): """Return the uncertain real number ``x``, that solves :math:`fn(x) = 0` The function fn() must take a single argument and x_min and x_max must define a range in which there is one (and only one) sign change in fn(x). The number 'epsilon' is a tolerance for accepting convergence. A RuntimeError will be raised if the root-search algorithm fails. An AssertionError will be raised if the preconditions for a search to begin are not satisfied. Parameters ---------- fn : a function of one argument x_min, x_max, epsilon : float Returns ------- UncertainReal .. versionadded:: 1.3.4 """ xk, dy_dx = nr_get_root(fn, x_min, x_max, epsilon) # In an implicit function F(x,...) = 0, where # we solve F = 0 by finding a value for `x`, # `x` depends implicitly on the other arguments. # The influence set of `F` and the influence set # of `x` should are the same. # `x` is not an elementary uncertain number; # it is implicitly a function of the other # arguments to F(). # The components of uncertainty of `x` are related to # the components of `F` as follows: # u_i(x) = -( dF/dx_i / dF/dx ) * u_i(xi) y = fn(UncertainReal._constant(xk)) dx_dy = -1 / dy_dx return UncertainReal(xk, vector.scale_vector(y._u_components, dx_dy), vector.scale_vector(y._d_components, dx_dy), vector.scale_vector(y._i_components, dx_dy))
def line_fit_wtls(x, y, u_x=None, u_y=None, a_b=None, r_xy=None): """Perform straight-line regression with uncertainty in ``x`` and ``y`` .. versionadded:: 1.2 :arg x: list of uncertain real numbers for the independent variable :arg y: list of uncertain real numbers for the dependent variable :arg u_x: a sequence of uncertainties for the ``x`` data :arg u_y: a sequence of uncertainties for the ``y`` data :arg a_b: a pair of initial estimates for the intercept and slope :arg r_xy: correlation between x-y pairs [default: 0] Returns a :class:`~type_b.LineFitWTLS` object The elements of ``x`` and ``y`` must be uncertain numbers with non-zero uncertainties. If specified, the optional arguments ``u_x`` and ``u_y`` will be used uncertainties to weight the data for the regression, otherwise the uncertainties of the uncertain numbers in the sequences are used. The optional argument ``a_b`` can be used to provide a pair of initial estimates for the intercept and slope. Otherwise, initial estimates will be obtained by calling `line_fit_wls`. Implements a Weighted Total Least Squares algorithm that allows for correlation between x-y pairs. See reference: M Krystek and M Anton, *Meas. Sci. Technol.* **22** (2011) 035101 (9pp) **Example**:: # Pearson-York test data # see, e.g., Lybanon, M. in Am. J. Phys 52 (1), January 1984 >>> xin=[0.0,0.9,1.8,2.6,3.3,4.4,5.2,6.1,6.5,7.4] >>> wx=[1000.0,1000.0,500.0,800.0,200.0,80.0,60.0,20.0,1.8,1.0] >>> yin=[5.9,5.4,4.4,4.6,3.5,3.7,2.8,2.8,2.4,1.5] >>> wy=[1.0,1.8,4.0,8.0,20.0,20.0,70.0,70.0,100.0,500.0] # Convert weights to standard uncertainties >>> uxin=[1./math.sqrt(wx_i) for wx_i in wx ] >>> uyin=[1./math.sqrt(wy_i) for wy_i in wy ] # Define uncertain numbers >>> x = [ ureal(xin_i,uxin_i) for xin_i,uxin_i in zip(xin,uxin) ] >>> y = [ ureal(yin_i,uyin_i) for yin_i,uyin_i in zip(yin,uyin) ] # TLS returns uncertain numbers >>> a,b = type_b.line_fit_wtls(x,y).a_b >>> a ureal(5.47991018...,0.29193349...,inf) >>> b ureal(-0.48053339...,0.057616740...,inf) """ N = len(x) if N != len(y): raise RuntimeError( "Different sequence lengths: len({!r}) != len({!r})".format(x, y)) if (u_x is not None or u_y is not None): if (u_x is None or u_y is None): raise RuntimeError("You must supply ``u_x`` and ``u_y``") elif (r_xy is None): # default value will be uncorrelated r_xy = [0] * len(u_x) if len(u_x) != N or len(u_y) != N: raise RuntimeError( "incompatible sequence lengths: {!r}, {!r}".format(u_x, u_y)) for x_i, y_i in izip(x, y): assert isinstance(x_i, UncertainReal), 'uncertain real required' assert isinstance(y_i, UncertainReal), 'uncertain real required' # Needed to define UNs locally ureal = lambda x, u: UncertainReal._elementary(x, u, inf, None, True) if a_b is None: a_b = line_fit_wls(x, y, u_y).a_b a0 = value(a_b[0]) b0 = value(a_b[1]) # initial value for `alpha` alpha0 = math.atan(b0) # chi_sq(alpha0) -> chisquared chi_sq = ChiSq(x, y, u_x, u_y, r_xy) # Search for the minimum chi-squared wrt alpha x1 = alpha0 - HALF_PI x2 = alpha0 + HALF_PI # `brent` requires three points that bracket the minimum. # the `x1`, `alpha0`, `x2` parameters should be real, # but `data` will return an uncertain number # and expects an uncertain number argument. # # Returns x, fn(x) and df_dx(x), all floats alpha1, fn_alpha1, df_alpha1 = _dbrent(x1, alpha0, x2, chi_sq) # dChiSq_a( alpha ) will return dChiSq_dalpha(`alpha`) # dChiSq_a(alpha0) -> 1st partial derivative of chisquared at alpha0 dChiSq_a = dChiSq_dalpha(x, y, u_x, u_y, r_xy) # Need the partial derivative of dChiSq_a wrt alpha alpha = ureal(alpha1, 1) F_alpha = dChiSq_a(alpha) dalpha_dF = -1.0 / F_alpha.sensitivity(alpha) # Now we define `alpha` with sensitivity to the ``x`` and ``y`` data, # via the object ``F_alpha``, which represents the 1st partial derivative # of chi-squared at alpha1 (ideally zero, but really only close to the root). F_alpha = dChiSq_a(UncertainReal._constant(alpha1)) alpha = UncertainReal(alpha1, scale_vector(F_alpha._u_components, dalpha_dF), scale_vector(F_alpha._d_components, dalpha_dF), scale_vector(F_alpha._i_components, dalpha_dF)) # The sensitivity of p_hat to the x and y data is via # `alpha`, `x_bar` and `y_bar` in eqn (43) p_hat = chi_sq.p_hat(alpha) # Note we have reversed the definitions of `a` and `b` here b = alpha._tan() a = p_hat / alpha._cos() N = len(x) ssr = chi_sq(UncertainReal._constant(alpha1)).x return LineFitWTLS(a, b, ssr, N)