Как найти все нули функции с помощью numpy (и scipy)?

Предположим, у меня есть функция f(x) между a и b. Эта функция может иметь много нулей, но также и много асимптот. Мне нужно вернуть все нули этой функции. Как лучше всего это сделать?

на самом деле, моя стратегия заключается в следующем:

  1. я оцениваю свою функцию на заданном количестве точек
  2. я обнаруживаю, есть ли изменение знака
  3. я нахожу ноль между точками что меняют знак
  4. Я проверяю, действительно ли найденный ноль равен нулю или это асимптота

    U = numpy.linspace(a, b, 100) # evaluate function at 100 different points
    c = f(U)
    s = numpy.sign(c)
    for i in range(100-1):
        if s[i] + s[i+1] == 0: # oposite signs
            u = scipy.optimize.brentq(f, U[i], U[i+1])
            z = f(u)
            if numpy.isnan(z) or abs(z) > 1e-3:
                continue
            print('found zero at {}'.format(u))
    

этот алгоритм, кажется, работает, за исключением двух потенциальных проблем:

  1. он не обнаружит ноль, который не пересекает ось x (например, в такой функции, как f(x) = x**2) однако, я не думаю, что это может произойти с функцией я оцениваю.
  2. Если точки дискретизации слишком далеко, между ними может быть больше одного нуля, и алгоритм может не найти их.

у вас есть лучшая стратегия (не менее эффективный), чтобы найти все нули функции?


Я не думаю, что это важно для вопроса, но для тех, кто интересуется, Я имею дело с характеристикой уравнений распространения волн в оптическом волокне. Функция выглядит так (где V и ell определены ранее, и ell - это положительные целое число):

def f(u):
    w = numpy.sqrt(V**2 - u**2)

    jl = scipy.special.jn(ell, u)
    jl1 = scipy.special.jnjn(ell-1, u)
    kl = scipy.special.jnkn(ell, w)
    kl1 = scipy.special.jnkn(ell-1, w)

    return jl / (u*jl1) + kl / (w*kl1)

2 ответов


основная проблема, которую я вижу с этим, если вы действительно можете найти все корни --- как уже упоминалось в комментариях, это не всегда возможно. Если вы уверены, что ваша функция не является полностью патологическим (sin(1/x) уже упоминалось), следующий - это ваша толерантность к отсутствию корня или нескольких из них. Иными словами, речь идет о том, насколько вы готовы пойти, чтобы убедиться, что вы не пропустили ни - - - насколько мне известно, нет общий метод, чтобы изолировать все корни для вас, так что вам придется сделать это самостоятельно. То, что вы показываете, уже разумный первый шаг. Несколько комментариев:

  • метод Брента действительно хороший выбор здесь.
  • прежде всего, разберитесь с расхождениями. Поскольку в вашей функции у вас есть Бессели в знаменателях, вы можете сначала решить для корни -- лучше искать их в например, Абрамовича и Stegun ( Mathworld link). Это будет лучше, чем использовать специальную сетку, которую вы используете.
  • что вы можете сделать, как только вы нашли два корня или расхождения,x_1 и x_2, запустите поиск снова в интервале [x_1+epsilon, x_2-epsilon]. Продолжайте, пока не будет найдено больше корней (метод Брента гарантированно сходится к a root, при условии, что есть один).
  • если вы не можете перечислить все расхождения, вы можете быть немного более осторожными в проверке кандидата действительно расхождение: дано x не просто проверить, что f(x) большой, проверьте это, например |f(x-epsilon/2)| > |f(x-epsilon)| для нескольких значений epsilon (1e-8, 1e-9, 1e-10, что-то в этом роде).
  • если вы хотите убедиться, что у вас нет корней, которые просто касаются нуля, ищите экстремумы функции и для каждого экстремума,x_e, проверьте значение f(x_e).

почему вы ограничились numpy? Составляющей есть пакет, который делает именно то, что вы хотите:

http://docs.scipy.org/doc/scipy/reference/optimize.nonlin.html

один урок, который я выучил: численное Программирование трудно, поэтому не делайте этого:)


в любом случае, если вы твердо настроены на создание алгоритма самостоятельно, страница doc на scipy I linked (занимает вечность для загрузки, кстати) дает вам список алгоритмов для начала. Один метод, который Я использовал раньше, чтобы дискретизировать функцию в степени, необходимой для вашей проблемы. (То есть настройте \delta x так, чтобы он был намного меньше характерного размера в вашей проблеме.) Это позволяет искать особенности функции (например, изменения знака). И вы можете вычислить производную отрезка линии (вероятно, с детского сада) довольно легко, поэтому ваша дискретизированная функция имеет четко определенную первую производную. Потому что вы настроили dx, чтобы быть меньше, чем характеристика размер, вы гарантированно не пропустите ни одной функции, которые важны для вашей проблемы.

Если вы хотите знать, что означает "характерный размер", найдите какой-либо параметр вашей функции с единицами длины или 1/length. То есть для некоторой функции f(x) предположим, что x имеет единицы длины, а f не имеет единиц. Затем ищите вещи, которые умножают X. Например, если вы хотите дискретизировать cos (\pi x), параметр, который умножает x (Если x имеет единицы длины), должен иметь единицы часть 1/длина. Таким образом, характерный размер cos(\pi x) равен 1/\pi. Если вы сделаете свою дискретизацию намного меньше, чем это, у вас не будет никаких проблем. Конечно, этот трюк не всегда будет работать, поэтому вам может понадобиться немного поработать.