Однажды появилась необходимость построить кривую по точкам...
Кривая 2-й степени
https://github.com/pravosleva/get-parabola
import Coeffs from 'get-parabola';
const lineFn = Coeffs.getLineByLeastSquaresApproximation([
{ x: 4, y: 4 },
{ x: 5, y: 5 },
{ x: 6, y: 6 },
{ x: 10, y: 7 },
{ x: 12, y: 8 },
{ x: 15, y: 9 },
]);
console.log(lineFn(5));
// 4.92487131748987Кривая 5-й степени
Для реализации аппроксимации методом наименьших квадратов (МНК) для полинома 5-й степени, нам нужно решить систему линейных уравнений вида:
S x A = Y
Где: S — матрица сумм степеней x, A — вектор искомых коэффициентов, Y — вектор свободных членов (суммы произведений степеней x на y).
Математическая справка
Для полинома пятой степени нам потребуется матрица размером 6x6. Использование ручного вычисления определителей (как в вашем примере для 2-й степени) для такой размерности крайне неэффективно и чревато ошибками. Лучше использовать метод Гаусса.
const byFifthDegreeLeastSquaresApproximation = (coordinatesArray) => {
const n = 6; // Количество коэффициентов для 5-й степени
const size = coordinatesArray.length;
// Создаем расширенную матрицу (n строк на n+1 столбцов)
const matrix = Array.from({ length: n }, () => new Array(n + 1).fill(0));
// 1. Заполняем матрицу суммами степеней x и произведений x^k * y
for (let i = 0; i < size; i++) {
const { x, y } = coordinatesArray[i];
for (let row = 0; row < n; row++) {
// Заполнение коэффициентов системы
for (let col = 0; col < n; col++) {
// Степень x будет ( (n-1-row) + (n-1-col) )
// Идем от x^10 до x^0
matrix[row][col] += Math.pow(x, (n - 1 - row) + (n - 1 - col));
}
// Заполнение правой части (свободных членов)
matrix[row][n] += Math.pow(x, n - 1 - row) * y;
}
}
// 2. Решение системы методом Гаусса (прямой и обратный ход)
for (let i = 0; i < n; i++) {
// Поиск максимального элемента в столбце (для устойчивости)
let max = i;
for (let k = i + 1; k < n; k++) {
if (Math.abs(matrix[k][i]) > Math.abs(matrix[max][i])) max = k;
}
[matrix[i], matrix[max]] = [matrix[max], matrix[i]];
// Приведение к треугольному виду
for (let k = i + 1; k < n; k++) {
const c = -matrix[k][i] / matrix[i][i];
for (let j = i; j <= n; j++) {
if (i === j) {
matrix[k][j] = 0;
} else {
matrix[k][j] += c * matrix[i][j];
}
}
}
}
// Обратный ход
const res = new Array(n).fill(0);
for (let i = n - 1; i >= 0; i--) {
res[i] = matrix[i][n] / matrix[i][i];
for (let k = i - 1; k >= 0; k--) {
matrix[k][n] -= matrix[k][i] * res[i];
}
}
// Возвращаем коэффициенты от x^5 до свободного члена
return {
a: res[0], // x^5
b: res[1], // x^4
c: res[2], // x^3
d: res[3], // x^2
e: res[4], // x^1
f: res[5] // константа
};
};
const getFifthDegreeLineByLeastSquaresApproximation = (points) => {
const coeffs = byFifthDegreeLeastSquaresApproximation(points);
const { a, b, c, d, e, f } = coeffs;
return x =>
(a * Math.pow(x, 5)) +
(b * Math.pow(x, 4)) +
(c * Math.pow(x, 3)) +
(d * Math.pow(x, 2)) +
(e * x) + f;
};Ключевые изменения:
- Универсальность: Код использует циклы для построения матрицы, что позволяет легко изменить степень полинома в будущем (просто изменив n).
- Метод Гаусса: Ручной расчет формул (как k1, k2, l в вашем примере) для 5-й степени занял бы сотни строк кода и был бы крайне нестабилен.
- Именование: Соблюдено ваше правило именования функций.
Обратите внимание: Полиномы высоких степеней (выше 3-4) очень чувствительны к точности данных и могут вести себя непредсказуемо между точками (эффект Рунге). Убедитесь, что у вас достаточно данных (минимум 6 точек) для построения такой кривой.
