问题
I have a function f(x)
, that is positive and decreasing for x<c
, and is zero for all x>=c
. How can I find c
, the threshold where the function hits zero (to within a tolerance)?
Here's an example:
zer = function(x){
ifelse(x>5, rep(0,length(x)), 5 - x)
}
> x=-5:15
> plot(x,zer(x))
You can use uniroot
to find where a function crosses zero, but that relies on the function being negative and positive on either side of the crossing, so I can't use that here. In the above case it evaluates zer
at the upper interval (15), finds a zero, and returns that.
I wrote a bisection algorithm that starts with the interval and moves left if f(midpoint) == 0
or right if f(midpoint) > 0
. This works, but I'm wondering if I've missed an implementation in a package or elsewhere that does this better, or if I've missed "one simple trick that lets you use uniroot to solve this".
The best I can find in the docs is the Numerical Task View's cryptic "There are implementations of the bisection algorithm in several contributed packages."
Note I don't have the gradient of f(x) so can't use Newton's method or anything that needs gradient evaluations at x values.
回答1:
One possibility - instead of returning 0 for f(x)==0, return a small constant negative number:
zer2 = function(x){
y = zer(x)
y[y==0]=-1e-6
y
}
this gives a solution that can be found with uniroot
:
> uniroot(zer2, c(-5,15))
$root
[1] 5.000043
The size of the small negative number might be important though.
Also, I'm not sure how well-behaved uniroot
is if half the function is a constant value of -1 - it seems to cope in this case and its probably robust enough.
回答2:
This problem seems to be well suited to a bisection method. We can do it something like this for example:
findroot = function(f, x, tol = 1e-9) {
if (f(x[2]) > 0) stop("No root in range")
if (f(x[1]) <= tol) return (x[1])
else {
xmid = mean(x)
if(f(xmid) == 0) x = c(x[1], xmid) else x = c(xmid, x[2])
return (findroot(f, x, tol))
}
}
findroot(zer, range(x))
# [1] 5
回答3:
I'm not sure this answers the question, but I've tried to follow your problem description to the letter:
f(x)
is positive and decreasing forx < c
, and is zero for allx >= c
;- find its zero to within a tolerance.
So I start with a little helper function, a variant of a function I generally use in order to avoid FAQ 7.31. Then it's a simple matter of keeping the first TRUE
it returns.
is.zero <- function(x, tol = .Machine$double.eps^0.5) abs(x) < tol
i <- which(is.zero(zer(x)))[1]
x[i]
#[1] 5
Off-topic.
The helper function mentioned is
is.equal <- function(x, y, tol = .Machine$double.eps^0.5) abs(x - y) < tol
This function is not equivalent to all.equal
since it is vectorized and the values of the shorter argument x
or y
recycle.
来源:https://stackoverflow.com/questions/47484556/find-threshold-value-where-fx-0