Js. Решатель

5 April

Завершим начатый пример и напишем функции для численного решения уравнений. Начнем с выбора уравнения, которое мы будем исследовать ,и для которого сначала выведем график - чтобы проверить полученные корни как по доступным онлайн-калькуляторам, так и по пересечению графика с осью абсцисс.

Попробуем кубическое уравнение - формула Кардано достаточно громоздка, чтобы это уравнение не хотелось решать точным аналитическим методом, но она есть, а это значит, что у нас имеется критерий правильности решения.

Итак, само уравнение:

Краткое описание уравнения на нашей странице
Краткое описание уравнения на нашей странице
Уравнение в JS
Уравнение в JS

Корни уравнения по коэффициентам можно определить по аналитической формуле здесь, в онлайн-калькуляторе.

Для краткости запишем уравнение через "стрелочную" функцию. Помимо более краткой записи, такие функции "подхватывают" в свой контекст выполнения родительский объект, и изнутри этих функций доступен this родителя без трюков с предварительным присваиванием self = this.

Это важно при написании обработчиков событий, а также функций внутри reduce, map и forEach. Но еще это просто кратко. Заранее вычислим квадрат и куб переменной x, чтобы убрать одну лишнюю операцию умножения, которая возникает при "наивной" записи x*x*x и x*x.

А теперь - решение. Сначала мы опробуем метод дихотомии. Подробнее прочитать про него можно здесь (вики по основам машинного обучения).

Если объяснять "на пальцах", то суть метода в том, чтобы на участке, где функция проходит через ноль и меняет знак (и уравнение обладает корнем), каждый шаг сравнивать знаки функции в начале отрезка, середине и на конце. Если знаки в начале и середине совпадают, то переход через ноль не происходит, и мы рассматриваем диапазон между серединой и концом.

И наоборот. Каждый раз участок поиска уменьшается вдвое, и так до тех пор, пока размер отрезка не станет сопоставим с заранее заданной погрешностью поиска (или разница между значениями функции на отрезке).

Эти принципы реализованы в функции dichotomie. Она принимает на вход границы отрезка поиска x0_start и x1_start, указатель на исследуемую функцию func и величину поисковой погрешности eps.

Функция поиска методом дихотомии
Функция поиска методом дихотомии

Каждый шаг записывается в массив, элементы которого хранят границы отрезков x0 и x1, значение функции в центре отрезка поиска f_05 (перед разбиением пополам) и номер шага поиска. Стоит обратить внимание, что в массив передается имя переменной. В ES6 такая сокращенная запись позволяет передать в объект поле, имя которого соответствует имени переменной, а значение - значению переменной.

Чтобы иметь больше материала для сравнения, реализуем еще один метод численного поиска. Если объяснять "на пальцах", то мы проводим к графику касательную, смотрим, где касательная уходит в ноль, оттуда снова проходим касательную и так до схождения. Использование касательной подразумевает, что мы будем вычислять значение производной, а поскольку функции на входе могут быть произвольными, то мы будем использовать не аналитические формулы, а определение производной.

...Производная функции f(x) в точке x0 - это отношение приращения значения функции f(x0 + eps) - f(x0 ) к приращению аргумента x0 + eps - x0 ...

Реализация метода Ньютона
Реализация метода Ньютона

Теперь паззл почти собран. Мы можем рисовать графики функций, мы можем находить нули этих функций. Теперь решим уравнения, для проверки выведем графики функций, а результаты запишем в таблицу и вставим эту таблицу в html

Динамическое создание html можно выполнять множеством способов. Это и document.write (не надо так делать, плиз. Ну хотя для отладки - то why not), это и свойство innerHTML вместе со строковыми литералами, про которые я упоминал в предыдущем примере, или создание и вставка HTML узлов.

Переменная-узел создается методом createElement глобального объекта document. Созданным узлом можно манипулировать любыми способами, изменяя стили, внутреннее содержимое узла, классы. Затем узел можно вставить в другой узел html методом appendChild.

Поскольку в отличие от численных расчетов, где количество шагов может переваливать за десятки тысяч (с десятками операций на каждом шаге), при выводе на экран количество элементов вряд ли перевалит за сотню-другую. Здесь читаемость кода все-таки важнее быстродействия, и важно получить не только работоспособный, но и читаемый, чистый и поддерживаемый код.

Для этого у нас есть метод массивов forEach. Этот метод поглощает функцию-callback, а затем последовательно применяет ее ко всем элементам массива. Намного чище, нагляднее и лаконичнее, чем кондовый цикл for, пусть и ценой несколько меньшей производительности

Динамическое создание и вывод таблиц
Динамическое создание и вывод таблиц

Функция outputTable принимает на вход массив данных и таблицу с форматом вывода данных (содержит имя столбца и количество цифр после запятой, которые нужно выводить для этого столбца).

Метод последовательно создает таблицу, затем заголовок, содержащий имена столбцов, а потом поэлементным проходам выводит в каждую строку таблицы данные о шаге вычислений. Этими строками заполняется таблица, указатель на которую возвращается в результате. После этого достаточно прицелиться querySelector-ом в соответствующий узел документа и через appendChild вывести таблицу на документ. Вот так:

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

Заодно покажем график исследуемой кубической параболы:

Кубическая парабола. Наглядно виден корень x = -1020, также видна возможность существования корней где-то рядом с нулем (см. график ниже)
Кубическая парабола. Наглядно виден корень x = -1020, также видна возможность существования корней где-то рядом с нулем (см. график ниже)
График исследуемой кубической параболы рядом с началом координат. Видны корни x=-1.63 и x = 5.647
График исследуемой кубической параболы рядом с началом координат. Видны корни x=-1.63 и x = 5.647

Вот так выглядит текстовый вывод решения:

поиск корня x=5.647
поиск корня x=5.647

И

Поиск корня x = -1020
Поиск корня x = -1020

Если кому-то интересно порыться в недрах примера, то добро пожаловать сюда, в гитхаб. Надеюсь, что пример был интересным и познавательным.