Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/image/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 递推高斯消去算法_Python_Algorithm_Python 3.x_Recursion_Linear Algebra - Fatal编程技术网

Python 递推高斯消去算法

Python 递推高斯消去算法,python,algorithm,python-3.x,recursion,linear-algebra,Python,Algorithm,Python 3.x,Recursion,Linear Algebra,我正在研究Python3中的函数编程概念,所以我编写了这个带尾部递归的高斯消去算法。该算法通过了我在教科书中找到的所有测试,只有一个例外。我必须编写简单的矩阵和向量类以及一些辅助函数,因为我使用的web平台没有NumPy(类在底部提供) 注意!根据我收到的第一个答案,我应该强调算术精度有一个截止点(你可以在底部的方框中找到数字) 算法 def istrue(iterable, key=bool): """ :type iterable: collections.Iterable

我正在研究Python3中的函数编程概念,所以我编写了这个带尾部递归的高斯消去算法。该算法通过了我在教科书中找到的所有测试,只有一个例外。我必须编写简单的矩阵和向量类以及一些辅助函数,因为我使用的web平台没有NumPy(类在底部提供)

注意!根据我收到的第一个答案,我应该强调算术精度有一个截止点(你可以在底部的方框中找到
数字

算法

def istrue(iterable, key=bool):
    """
    :type iterable: collections.Iterable
    :type key: (object) -> bool
    :rtype: tuple[bool]
    """
    try:
        return tuple(map(int, map(key, iterable)))
    except TypeError:
        return bool(iterable),


def where(iterable, key=bool):
    """
    :type iterable: collections.Iterable
    :type key: (object) -> bool
    :rtype: tuple[int]
    """
    return tuple(i for i, elem in enumerate(iterable) if key(elem))


def next_true(iterable, i, key=bool):
    """
    Returns position of a True element next to the i'th element
    (iterable[j]: j>i, key(iterable[j]) -> True) if it exists.
    :type iterable: collections.Iterable
    :type i: int
    :rtype: int | None
    """
    true_mask = istrue(iterable, key)
    try:
        return where(enumerate(true_mask),
                     key=lambda ind_val: ind_val[0] > i and ind_val[1])[0]
    except IndexError:
        # No elements satisfy the condition
        return None

def row_echelon(matrix):
    """
    Transforms matrix into the row echelon form
    :type matrix: Matrix
    :return: upper-triangular matrix
    :rtype: Matrix
    """
    @optimize_tail_recursion
    def operator(m, var):
        """
        :type m: Matrix
        :type var: int
        :rtype: Matrix
        """
        # if we have already processed all variables we could
        if var > m.ncol - 1 or var > m.nrow - 1:
            return m
        # if matrix[var][var] is zero and there exist i such that
        # matrix[i][var] > 0, i > j
        elif not m[var][var] and sum(istrue(m.col(var))[var:]):
            i = next_true(istrue(m.col(var)), var)
            return operator(m.permute(var, i), var)
        # if |{matrix[i][var], 0 <= i < nrow(matrix)}| > 1, matrix[var][var]!=0
        elif m[var][var] and sum(istrue(m.col(var))) - 1:
            i = tuple(ind for ind in where(m.col(var)) if ind != var)[0]
            coeff = - m[i][var] / m[var][var]
            return operator(m.add_rows(var, i, coeff), var)
        # if matrix[var][var] is zero and there is no i such that
        # matrix[i][var] > 0, i > j
        # or all possible elements were eliminated
        return operator(m.normalise_row(var, var), var+1)

    return operator(matrix, 0)


def solve_linear_system(augmented_matrix):
    """
    :type augmented_matrix: Matrix
    :return: str | tuple[float]
    """
    row_echelon_m = row_echelon(augmented_matrix)
    print(row_echelon_m)
    left_side, right_side = (row_echelon_m[:, :row_echelon_m.ncol-1],
                             row_echelon_m.col(-1))
    nontrivial = istrue(left_side, key=lambda row: bool(sum(row)))
    rank = sum(nontrivial)

    # if there exists an equation with trivial coefficients and nontrivial
    # right-hand side
    if sum(not sum(l_side) and r_side for l_side, r_side
           in zip(left_side, right_side)):
        return NO

    # rank(matrix) < number of variables
    elif rank < left_side.ncol:
        return INF
    return right_side
让我们看看它是如何一步一步地变换矩阵的:

solve_linear_system(test6) # -> 

# The integer on the right side denoted the variable that is being eliminated at the moment using the corresponding row. 

((3.0, -5.0, 2.0, 4.0, 2.0),
 (7.0, -4.0, 1.0, 3.0, 5.0),
 (5.0, 7.0, -4.0, -6.0, 3.0)) 0

((3.0, -5.0, 2.0, 4.0, 2.0),
 (0.0, 7.666666666666668, -3.666666666666667, -6.333333333333334, 0.33333333333333304),
 (5.0, 7.0, -4.0, -6.0, 3.0)) 0

((3.0, -5.0, 2.0, 4.0, 2.0),
 (0.0, 7.666666666666668, -3.666666666666667, -6.333333333333334, 0.33333333333333304),
 (0.0, 15.333333333333334, -7.333333333333334, -12.666666666666668, -0.3333333333333335)) 0

((1.0, -1.6666666666666665, 0.6666666666666666, 1.3333333333333333, 0.6666666666666666),
 (0.0, 7.666666666666668, -3.666666666666667, -6.333333333333334, 0.33333333333333304),
 (0.0, 15.333333333333334, -7.333333333333334, -12.666666666666668, -0.3333333333333335)) 1

((1.0, 0.0, -0.13043478260869557, -0.043478260869564966, 0.7391304347826085),
 (0.0, 7.666666666666668, -3.666666666666667, -6.333333333333334, 0.33333333333333304),
 (0.0, 15.333333333333334, -7.333333333333334, -12.666666666666668, -0.3333333333333335)) 1

((1.0, 0.0, -0.13043478260869557, -0.043478260869564966, 0.7391304347826085),
 (0.0, 7.666666666666668, -3.666666666666667, -6.333333333333334, 0.33333333333333304),
 (0.0, 0.0, -8.88e-16, -1.776e-15, -0.9999999999999994)) 1

((1.0, 0.0, -0.13043478260869557, -0.043478260869564966, 0.7391304347826085),
 (0.0, 0.9999999999999999, -0.4782608695652173, -0.826086956521739, 0.04347826086956517),
 (0.0, 0.0, -8.88e-16, -1.776e-15, -0.9999999999999994)) 2

((1.0, 0.0, 0.0, 0.21739130434782616, 146886016451234.4),
 (0.0, 0.9999999999999999, -0.4782608695652173, -0.826086956521739, 0.04347826086956517),
 (0.0, 0.0, -8.88e-16, -1.776e-15, -0.9999999999999994)) 2

((1.0, 0.0, 0.0, 0.21739130434782616, 146886016451234.4),
 (0.0, 0.9999999999999999, 0.0, 0.13043478260869557, 538582060321190.4),
 (0.0, 0.0, -8.88e-16, -1.776e-15, -0.9999999999999994)) 2

((1.0, 0.0, 0.0, 0.21739130434782616, 146886016451234.4),
 (0.0, 0.9999999999999999, 0.0, 0.13043478260869557, 538582060321190.4),
 (-0.0, -0.0, 0.9999999999999999, 1.9999999999999998, 1126126126126125.2)) 3
我得到了一个有无穷多解的相容系统。我觉得一切都很好,但我应该得到这个不一致的系统(我用NumPy和R检查过):

我希望我遗漏了一些明显而简单的东西。我希望代码有足够好的文档记录,以便可读。先谢谢你

类别、辅助函数和常数:

NO = "NO"
YES = "YES"
INF = "INF"

DIGITS = 16

def typemap(iterable, types):
    try:
        return all(isinstance(elem, _type)
                   for elem, _type in zip(iterable, types))
    except TypeError:
        return False

class TailRecursionException(BaseException):
    def __init__(self, args, kwargs):
        self.args = args
        self.kwargs = kwargs

def optimize_tail_recursion(g):
    def func(*args, **kwargs):
        f = sys._getframe()
        if f.f_back and f.f_back.f_back and f.f_back.f_back.f_code == f.f_code:
            raise TailRecursionException(args, kwargs)
        else:
            while 1:
                try:
                    return g(*args, **kwargs)
                except TailRecursionException as e:
                    args = e.args
                    kwargs = e.kwargs
    func.__doc__ = g.__doc__
    return func

class Vector(tuple):
    def __add__(self, other):
        if not isinstance(other, tuple):
            raise TypeError
        return Vector(round(a + b, DIGITS) for a, b in zip(self, other))

    def __radd__(self, other):
        return self.__add__(other)

    def __sub__(self, other):
        return self.__add__(-1 * other)

    def __rsub__(self, other):
        return self.__sub__(other)

    def __mul__(self, other):
        if not isinstance(other, (int, float)):
            raise TypeError
        return Vector(round(elem * round(other, DIGITS), DIGITS) for elem in self)

    def __rmul__(self, other):
        return self.__mul__(other)

    def extend(self, item):
        return Vector(super().__add__((item, )))

    def concat(self, item):
        return Vector(super().__add__(item))


class Matrix:
    """
    :type _matrix: tuple[Vector]
    """
    def __init__(self, matrix):
        """
        :type matrix: list[list] | tuple[tuple]
        :return:
        """
        if not matrix:
            raise ValueError("Empty matrices are not supported")
        self._matrix = tuple(map(Vector, map(lambda row: map(float, row),
                                             matrix)))

    def __iter__(self):
        return iter(self._matrix)

    def __repr__(self):
        return "({})".format(",\n ".join(map(repr, self)))

    def __getitem__(self, item):
        """
        Returns a Vector if item is int or slice; returns a Matrix if item is
        tuple of slices
        :param item:
        :rtype: Matrix | Vector
        """
        if isinstance(item, (int, slice)):
            return self._matrix[item]
        elif typemap(item, (slice, slice)):
            row_slice, col_slice = item
            return Matrix(tuple(map(op.itemgetter(col_slice), self[row_slice])))

    def __mul__(self, coeff):
        if not isinstance(coeff, (int, float)):
            raise TypeError
        return Matrix(tuple(vector * coeff for vector in self))

    def __rmul__(self, coeff):
        return self.__mul__(coeff)

    @property
    def nrow(self):
        return len(self._matrix)

    @property
    def ncol(self):
        return len(self.row(0))

    def row(self, i):
        return self[i]

    def col(self, j):
        """
        :type j: int
        :return: tuple[tuple]
        """
        return Vector(self[i][j] for i in range(self.nrow))

    def _replace_row(self, i, replacement):
        new_matrix = tuple(self.row(_i) if _i != i else replacement
                           for _i in range(self.nrow))
        return Matrix(new_matrix)

    def permute(self, a, b):
        """
        Exchange rows a and b
        :type a: int
        :type b: int
        :rtype: Matrix
        """
        return self._replace_row(b, self.row(a))._replace_row(a, self.row(b))

    def multiply_row(self, i, coeff):
        return self._replace_row(i, self.row(i) * coeff)

    def normalise_row(self, i, j):
        coeff = 1 / (self[i][j]) if self[i][j] else 1
        return self.multiply_row(i, coeff)

    def add_rows(self, a, b, coeff=1):
        """
        :return: matrix': matrix'[b] = coef * matrix[a] + matrix[b]
        :rtype: Matrix
        """
        return self._replace_row(b, coeff * self.row(a) + self.row(b))

我在最后几步看到一些非常大和非常小的数字;似乎您是舍入错误的受害者,生成的不是0而是非常小的数字(以及它们非常大的倒数)

您需要设置一个(相对)截止值,低于该值的数字将被视为零(2e-15似乎是一个不错的数字)。找到系统的ε:两个浮点数之间的最小差值,可以正确表示,且为1阶

检查方法,如
normalise_row()
,您可以这样做

coeff = 1 / (self[i][j]) if self[i][j] else 1
还有一些我看不到使用
round()
的地方


另一种可能是使用Python模块。我对它没有太多的经验,但也许它为您的计算提供了必要的精度。

我只是快速查看了一下算法,它似乎在做您期望的事情。真正的问题在于如何检测不一致解决方案的“机制”。如果你看一下算法的第六步,你会发现

 ((1.0, 0.0, -0.13043478260869557, -0.043478260869564966, 0.7391304347826085),
  (0.0, 7.666666666666668, -3.666666666666667, -6.333333333333334, 0.33333333333333304),
  (0.0, 0.0, -8.88e-16, -1.776e-15, -0.9999999999999994))
请注意,与其他数字相比,第三行的第三和第四项非常小。事实上,它们只是四舍五入的错误。如果你用精确的数字做减法运算,那么第二列会给你46/3-2*23/2=0(你得到),但是第三列会给你-22/3-2*(-11/3)=0,因为你没有得到。第四列也是如此。然后在下一个阶段中,通过将这些零“缩放”到1和2来复合误差,从那时起,所有零都将处于误差中


您有两种选择-要么使用精确的数字进行计算(我不知道python是否可以做到这一点,但是,例如,在Scheme中,存在不会产生此问题的精确分数-可能您必须自己滚动…),或者,使用足够高精度的算法,任意将小于某个选定值(最好基于原始矩阵中数字的大小)的任何值设置为零。如果矩阵真的有一个数量变化幅度很大的解决方案,那么这将失败,但在“现实世界”问题中,它应该可以消除您遇到的问题。

感谢您的反馈。有一个16位的截止设置(底部块中的
常数)。我已经尝试将截止值减少到14(及以下),但没有任何积极影响14位数字听起来可能会正确舍入,尽管您可能希望使其更适合未来(您的数字是1阶的,所以舍入有效,但如果它们是1000阶的,14位数字基本上等于3位有效数字)。但是您也可能在其他地方错过了一个round()(您的矩阵类似乎没有使用round())。所有算术运算都是通过Vector类实现的,因此它们最终都会调用
Vector.\uuuuu add\uuuu
Vector.\uuuu mul\uuuu
。无论输入是什么,这两种操作都使用数字对其输出进行四舍五入。你似乎是对的,但我不知道如何使其更精确。无论如何,至少我知道问题出在哪里+1我不确定我引用的行是否使用了向量:
self[I][j]
似乎是一个单一的矩阵元素,而不是向量,因此除法只是浮点除法,没有舍入。但数字截断似乎并不适用于所有地方。@Evert确实如此。所有算术运算都是通过
Vector
类实现的,因此它们最终都会调用
Vector.\uuuu add\uuuu
Vector.\uuuu mul\uuuu
。这两种操作都使用
数字
对其输出进行四舍五入。您似乎是对的,但我不知道如何使其更精确。无论如何,至少我知道问题出在哪里+1一个问题是高斯-乔丹消去法易受舍入误差的影响。如果你看一看像“数字配方在……中”这样的文本,他们推荐像奇异值分解和LU分解这样的算法。这些在四舍五入时更稳定(在SVD的情况下,SVD可以“解决”过度约束和欠约束系统,但它们的编码要复杂得多。如果你坚持使用GJ,那么与其使其“更精确”,不如强制一个小结果为零,在这一点上,你作为例子使用的矩阵已经明显不一致。
coeff = 1 / (self[i][j]) if self[i][j] else 1
 ((1.0, 0.0, -0.13043478260869557, -0.043478260869564966, 0.7391304347826085),
  (0.0, 7.666666666666668, -3.666666666666667, -6.333333333333334, 0.33333333333333304),
  (0.0, 0.0, -8.88e-16, -1.776e-15, -0.9999999999999994))