【Python】任意边界条件生成多项式基函数的算法(一维)

【Python】任意边界条件生成多项式基函数的算法(一维),第1张

一、理论背景

        微分方程是描述客观世界强有力的工具,它可以反映事物某些内在属性与外在表象之间的关系。但其实微分方程既不好写,也不好解,比如麦克斯韦站在法拉第、汤姆逊等实验大家的肩膀上,凭借他高深的数学造诣和丰富的想象力也花了十余年才将大笔一挥,写下麦克斯韦方程组,一统电磁。还有至今俯视人类的纳维斯托克斯方程组,170余年的时光里,很多人为之奉献了一生的努力,但遗憾的是,我们仍未能找到解析解。 虽然微分方程解析解不易求得,但在工程领域广泛应用的数值解、近似解,一直在迅猛发展。

        求微分方程的近似解的思路是,先取原函数的近似解,将近似代入微分方程,即可将复杂的微分方程转化为一般的线性方程组,而求解线性方程组就是简单的事情了。

        取近似解的思路是,先确定一组线性无关的基函数,再线性叠加基函数,就可获得近似解。 工程领域常用的傅里叶展开,就是选取一组线性无关的三角函数作为基函数,线性叠加基函数后,就获得了原函数的近似解。

另一种常用的方式是将多项式作为基函数。

由泰勒展开可知,所取多项式的项数越高,则近似解会越逼近原函数

        但有得必有舍,因为所取多项式的系数均为未知数,若取的项数越多,则产生的未知数也越多,而解出多项式系数的线性方程组依赖的是边界条件,n个条件解n个未知数,所以当边界条件数较少时,所取多项式的项数也会有限制,本文将展示一种基于Sympy库的,针对一维问题,给定任意边界条件,生成基函数的算法


算法主要思想:

  1. n个边界条件可以确定n个未知数,但为了满足近似解的任意性,所以设n个边界条件确定了一个最高n次的多项式(n+1项),即整个式子带有一个未知数,(根据实际情况,也可预留多位未知数)
  2. 以考虑导数的拉格朗日思想(解决了荣格现象)进行插值,求解出n个未知数

算法主要步骤:

  • 以类的形式管理边界条件
  • 根据边界条件个数确定近似解多项式
  • 将边界条件代入近似解多项式,获得代数方程组
  • 提取方程组系数
  • 考虑存在微分的边界条件情况,需要根据阶数进行多次微分以获得系数
  • 合并方程组系数为矩阵,以矩阵形式求解方程组
  • 将解出的系数依次代回近似解中,获得仅剩一个未知数的近似解表达式

二、代码实现

2.1以类的形式封装边界条件的信息,便于后续使用该条件的各项信息

        单独写入boundary_condition文件,以下为该文件完整代码

class BoundaryCondition:
    def __init__(self,
                 list_x: list,  # 自变量的list
                 value,  # 该边界条件的值
                 list_rank: list,  # 各项的微分次数list
                 list_factor: list,  # 各项的系数
                 list_relation: list  # 各项与总表达式的关系(加减乘除)
                 ):
        self.list_x = list_x
        self.value = value
        self.list_rank = list_rank
        self.list_factor = list_factor
        self.list_relation = list_relation

        将以下所有方法写入base_expression文件,统一管理

2.2根据边界条件个数确定近似解多项式

        此处用到了一个自动循环定义符号变量的技巧

import sympy as sp
# 设置全局精度变量,控制每次赋值运算的有效位数
precision_value = 5

def get_basic_approximate_expression(condition_num):
    """ 生成底层的近似u函数,传入已知边界条件个数,n个边界条件生成的插值函数最高n次 """
    expression_u = 0
    for i in list(range(condition_num + 1)):  # 基于边界条件个数n,循环生成n个多项式基函数
        index = "a" + str(i)
        item = sp.symbols('%s' % index)
        x = sp.symbols('x')
        expression_u += item * x ** i
    return expression_u

2.3将边界条件的微分信息计算到近似解多项式的表达式中

        若为0阶,不微分,高阶则进行循环微分

def get_expression_from_boundary(expression, rank):
    """ 从边界条件充实底层近似函数 """
    temp_expression = expression
    if rank != 0:  # 微分次数不为0时,进行循环微分
        for _ in list(range(rank)):
            temp_expression = sp.diff(temp_expression, 'x')
    return temp_expression

2.4将边界条件的自变量取值的信息计算到近似解多项式的表达式中

def get_equation_from_boundary(expression, value_x, precision=precision_value):
    """ 从边界条件获取仅含系数a的方程 """
    return expression.subs('x', value_x, n=precision)

2.5求解a系数的关系

        传入方程组以及对应的值,通过判断方程组未知数的个数来确定待消元的n个未知数,最后求解方程,返回a系数之间的关系(所有未知数可以由一个未知数来表示了)

def get_factor_relation(list_equation, list_value):
    """ 解方程,获取a系数之间的关系 """
    if len(list_equation) == len(list_value):  # 长度一致说明传入的格式正确
        num_list = []  # 用来存储未知数个数
        for equation in list_equation:
            string_equation = str(equation)  # 将方程组转化为字符形式
            num_list.append(string_equation.count('a'))  # 获取字符形式方程中未知数的个数,依次判断每个方程的未知数个数
        item_num = max(num_list)  # 只需知道未知数个数的最大值,因为只用来作循环的判断条件
        list_unknown_num = []  # 用来存储待消元的未知数
        complete_equation_list = []
        n = len(list_equation) + 1  # n个方程说明实际有n+1个未知数
        from_num = n - item_num  # 用来判断,是否有未知数在之前的代值过程中被消掉了
        # 如果最大值已经小于未知数个数,说明方程是可解的!
        if n - item_num == 0:
            for i in list(range(n)):
                index = "a" + str(i)
                list_unknown_num.append(index)
        elif n - item_num > 0:
            for i in list(range(from_num, n)):  # 此处还存在瑕疵,存在一个很强的假定条件:最后的an没有被消掉
                index = "a" + str(i)
                list_unknown_num.append(index)
        for j in list(range(len(list_equation))):
            temp = list_equation[j] - list_value[j]
            complete_equation_list.append(temp)
        return sp.solve(complete_equation_list, list_unknown_num)

2.6传入系数间的关系、基函数,插值出多项式表达式

def get_upper_approximate_expression(factor_relation, basis_expression):
    """ 插值出近似解的多项式表达式 """
    high_expression = basis_expression
    string_equation = str(high_expression)
    item_num = string_equation.count('a')  # 获取底层近似函数中未知数的个数
    list_index = []  # 存储全部ai数列符号
    list_result_key = []
    for i in list(range(item_num)):
        list_index.append("a" + str(i))
    for key in factor_relation:
        list_result_key.append(str(key))
    for index in list_index:
        # 将表达式替换为用一个a表示
        if index in list_result_key:
            sym = sp.symbols(index)
            high_expression = high_expression.subs({sym: factor_relation[sym]})
        # 需要将剩余的未知数置为1时,请取消注释
        # elif len(list_index) - len(list_result_key) == 1:  # 将其赋为1
        #     high_expression = high_expression.evalf(subs={index: 1})
        # else:
        #     high_expression = high_expression.evalf(subs={index: 0})
    return high_expression

2.7集成上面的方法,统一处理,最终返回一个多项式表达式

def get_approximate_expression(list_boundary_condition):
    """ 主函数,集成运算后返回最终的多项式表达式 """
    list_equation = []
    list_value = []
    basic_approximate_expression = get_basic_approximate_expression(len(list_boundary_condition))
    for boundary in list_boundary_condition:
        # 把边界条件的值放到统一的list中
        list_value.append(boundary.value)
        # 获取此边界条件的项数
        item_num = len(boundary.list_x)
        equation = 0
        for i in list(range(item_num)):
            expression = get_expression_from_boundary(basic_approximate_expression,
                                                      rank=boundary.list_rank[i])
            temp_equation = get_equation_from_boundary(expression=expression, value_x=boundary.list_x[i])
            # 拼接到此边界条件的大方程中去
            if boundary.list_relation[i] == "add":  # 加法标记符,执行加法
                equation = equation + boundary.list_factor[i] * temp_equation
            elif boundary.list_relation[i] == "sub":  # 减法标记符,执行减法
                equation = equation - boundary.list_factor[i] * temp_equation
            elif boundary.list_relation[i] == "tim":  # 乘法法标记符,执行乘法
                equation = equation * boundary.list_factor[i] * temp_equation
            elif boundary.list_relation[i] == "div":  # 除法标记符,执行除法
                equation = equation / boundary.list_factor[i] * temp_equation
        list_equation.append(equation)  # 执行一次循环后,将处理好的方程保存到方程list中
    factor_relation = get_factor_relation(list_equation, list_value)
    return get_upper_approximate_expression(factor_relation, basis_expression=basic_approximate_expression)

三、示例演示 示例1:

1.边界条件:

2.main文件设置:

from boundary_condition import BoundaryCondition
import base_expression as be

# 设置边界条件1
boundary_1 = BoundaryCondition(list_x=[0],  # 设置边界条件的自变量
                               value=0,  # 设置边界条件的值
                               list_rank=[0],  # 设置边界条件中各项的阶数
                               list_factor=[1],  # 设置边界条件中各项的系数
                               list_relation=["add"])  # 设置边界条件中各项的关系
# 设置边界条件2
boundary_2 = BoundaryCondition(list_x=[1],
                               value=0,
                               list_rank=[1],
                               list_factor=[1],
                               list_relation=["add"])

result = be.get_approximate_expression([boundary_1, boundary_2])
print("插值出的多项式表达式", result)

3.结果: 


示例2:

1.边界条件:

 

2.main文件设置:

from boundary_condition import BoundaryCondition
import base_expression as be

# 设置边界条件1
boundary_1 = BoundaryCondition(list_x=[0],  # 设置边界条件的自变量
                               value=0,  # 设置边界条件的值
                               list_rank=[0],  # 设置边界条件中各项的阶数
                               list_factor=[1],  # 设置边界条件中各项的系数
                               list_relation=["add"])  # 设置边界条件中各项的关系
# 设置边界条件2
boundary_2 = BoundaryCondition(list_x=[1],
                               value=1,
                               list_rank=[0],
                               list_factor=[1],
                               list_relation=["add"])

result = be.get_approximate_expression([boundary_1, boundary_2])
print("插值出的多项式表达式", result)

3.结果: 


示例3:

1.边界条件:

2.main文件设置:

from boundary_condition import BoundaryCondition
import base_expression as be

# 设置边界条件1
boundary_1 = BoundaryCondition(list_x=[0, 1],  # 设置边界条件的自变量
                               value=7,  # 设置边界条件的值
                               list_rank=[0, 0],  # 设置边界条件中各项的阶数
                               list_factor=[1, 2],  # 设置边界条件中各项的系数
                               list_relation=["add", "add"])  # 设置边界条件中各项的关系
# 设置边界条件2
boundary_2 = BoundaryCondition(list_x=[0, 2],
                               value=-1,
                               list_rank=[1, 2],
                               list_factor=[1, -1],
                               list_relation=["add", "add"])

result = be.get_approximate_expression([boundary_1, boundary_2])
print("插值出的多项式表达式", result)

3.结果: 


四、补充

此算法尚有不足,存在两个问题

  • 针对乘除法等非线性组合,还未处理,目前只是预留了一个框架
  • 求解a系数时,由于暂时未找到合适的方法跟踪系数,所以难以完全对应任意情况,此处硬编码假定了最后一位下标最大的a系数不会在代入边界条件的过程中被消掉,不过也足够处理大部分情况

后续会继续优化


源码github地址:https://github.com/base_function

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/800638.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-07
下一篇 2022-05-07

发表评论

登录后才能评论

评论列表(0条)

保存