Я хочу решить матрицу с помощью PHP. Например, если у меня есть три точки: (x0, y0), (x1, y1) and (x2, y2), я хочу знать, что p[0], p[1] and p[2] находится в y = p[2]*x^2 + p[1]*x^1 + p[0]*x^0, действительное для всех этих точек. Если дано n баллов, я хочу решить y = p[n] * x^n + p[n-1] * x^(n-1) + ... + p[0] * x^0. Что у меня есть на данный момент, это:

<?php

$system = new EQ();
$system->add(1, 2);
$system->add(4, 5);
$system->solvePn(0);

class EQ {

    private $points = array();

    public function add($x, $y) {
        $this->points[] = array($x, $y);
    }

    public function solvePn($n) {
        // Solve p[n]
        // So eliminate p[m], p[m-1], ..., p[n+1], p[n-1], ..., p[1], p[0]
        $m = count($this->points);
        $a = $m;
        // Eliminate p[a]
        if ($a != $n) {

        }
        $a--;
    }

}
?>

Но теперь я не знаю, что делать дальше.

0
www.data-blogger.com 29 Мар 2011 в 16:18
2
Не существует такой вещи, как «решение матрицы», слово (слова), которые вы ищете, — это полиномиальная интерполяция, а «что делать дальше» зависит от огромного количества методов интерполяции, основанных на числах условий и потерях точности. как далеко вы хотите зайти в этом, потому что тема непростая?
 – 
davin
29 Мар 2011 в 16:27
Это не должно быть очень точным. Какие методы существуют для этого?
 – 
www.data-blogger.com
29 Мар 2011 в 16:31
1
Метод Ньютона, вероятно, будет самым простым в реализации (хотя это только из тех немногих, с которыми я знаком: прямой вывод, который, похоже, вы пытались сделать только с матрицей, непосредственно с полиномами Лагранжа и метод Ньютона)
 – 
davin
29 Мар 2011 в 16:37
Он решает n уравнений с n неизвестными, а не полиномиальной интерполяцией. Форма, в которой он это записал, не имеет значения, все Xn и Yn являются константами, поэтому вы получаете старую добрую матрицу.
 – 
Roman Zenka
29 Мар 2011 в 17:33
1
@Roman, во-первых, то, что описано в OP, является [почти] определением полиномиальной интерполяции, так что да, это то, что он делает (очевидно, x и y постоянны, они также постоянны при интерполяции). Вы правы, решение с помощью матрицы — это один способ сделать это, хотя он неэффективен, негибок и не позволяет значительно снизить потери точности.
 – 
davin
29 Мар 2011 в 21:41

1 ответ

Спасибо Давин и Роман. Я использовал для этого Лагранжа, и теперь он работает нормально. Например, если задано 3 точки (1,1), (2,3), (3,27), класс будет использовать Лагранжа для вычисления полиномиальной аппроксимации. Теперь вы можете вызвать $system->f(4), чтобы вычислить значение y для x=4 на этом полиноме.

<?php

$system = new EQ();
$system->add(1, 1);
$system->add(2, 3);
$system->add(3, 27);
echo $system->f(4);

class EQ {

    private $points = array();
    private $formula = NULL;

    public function add($x, $y) {
        $this->points[] = array($x, $y);
    }

    public function lz($z) {
        $point = $this->points[$z];
        // Get the x and y value of this point
        $x = $point[0];
        $y = $point[1];
        // Now get all points except these and build the formula
        $index = 0;
        $above = '';
        $below = 1;
        foreach ($this->points as $point) {
            if ($index != $z) {
                $xp = $point[0];
                $yp = $point[1];
                $above .= '(x-' . $xp . ')';
                $below *= ($x - $xp);
            }
            $index++;
        }
        $factor = $y / $below;
        $above = ungroup($above);
        foreach ($above as $degree=>$subfactor) {
            $above[$degree] = $subfactor * $factor;
        }
        return $above;
    }

    public function f($x) {
        if ($this->formula === NULL) $this->L();
        $formula = $this->formula;
        $val = 0;
        foreach ($formula as $degree=>$factor) {
            $subval = $factor * pow($x, $degree);
            $val += $subval;
        }
        return $val;
    }

    public function L() {
        $n = count($this->points);
        $formula = array();
        for ($z = 0; $z < $n; $z++) {
            $l = $this->lz($z);
            foreach ($l as $degree=>$factor) {
                $formula[$degree] += $factor;
            }
        }
        $this->formula = $formula;
        return $formula;
    }

}

// Transform a group-formula to a list with terms
// @example (x-1)*(x-2)
function ungroup($formula) {
    preg_match_all('/\(([^)]{1,})\)/', $formula, $matches);
    $groups = $matches[1];
    $factorTerms = getTerms(reset($groups));
    while (key($groups) < count($groups) - 1) {
        next($groups);
        $terms = getTerms(current($groups));
        $newTerms = array();
        foreach ($terms as $term) {
            foreach ($factorTerms as $factorTerm) {
                $degree = getDegree($term) + getDegree($factorTerm);
                $factor = getFactor($term) * getFactor($factorTerm);
                $newTerm = '';
                if ($factor != 1) $newTerm = ($factor == -1?'-':$factor);
                if ($degree != 0) $newTerm .= 'x' . ($degree == 1?'':'^' . $degree);
                if (strlen($newTerm) == 0) $newTerm = '0';
                $newTerms[] = $newTerm;
            }
        }
        $factorTerms = $newTerms;
    }
    $terms = array();
    foreach ($factorTerms as $term) {
        $degree = getDegree($term);
        $factor = getFactor($term);
        $terms[$degree] += $factor;
    }
    return $terms;
}

function getFactor($term) {
    if (strpos($term, 'x') !== false) {
        $pattern = '/([0-9\-]{1,})[\*]?x/';
        preg_match($pattern, $term, $matches);
        if (count($matches) == 2) {
            $n = $matches[1];
            if ($n === '-') return -1;
            return $n;
        }
        return 1;
    }
    return $term;
}

function getDegree($term) {
    if (strpos($term, 'x') !== false) {
        $pattern = '/x\^([0-9\-]{1,})/';
        preg_match($pattern, $term, $matches);
        if (count($matches) == 2) {
            return $matches[1];
        }
        return 1;
    }
    return 0;
}

function getTerms($group) {
    $group = str_replace('-', '+-', $group);
    $group = preg_replace('/\+{1,}/', '+', $group);
    $terms = explode('+', $group);
    return $terms;
}
?>
0
www.data-blogger.com 29 Мар 2011 в 18:00
Ааарг, ненавижу полиномиальное время... Есть ли более быстрый способ сделать это для многих точек?
 – 
www.data-blogger.com
29 Мар 2011 в 18:03
Я не знаю, сможете ли вы стать лучше, чем O (n ^ 2), хотя, если вы настроите базовую систему и разрешите добавления с помощью метода Ньютона, вы можете добавлять точки по отдельности за O (n), поэтому при определенных обстоятельствах вместо вычислив две системы за O(n^2) или O(n^3) дважды, вы можете вычислить базовую систему за полиномиальное время, а затем добавить точки за небольшой промежуток времени, т.е. за линейное время.
 – 
davin
29 Мар 2011 в 21:47
Это выглядит намного сложнее, чем я ожидал... вы создаете большую строку с формулой только для того, чтобы разобрать ее на части на следующем шаге?
 – 
Roman Zenka
30 Мар 2011 в 20:00