【Python】常用数值方法的python实现

目录

解非线性方程

方法综述

  • 使用SymPy符号计算库中的solve()(解析解)/nsolve()(数值解)函数。
  • 一元方程数值解可使用迭代法辅助求解。

问题分类

求解一元方程

问题引入:求该方程数值解

【Python】常用数值方法的python实现

解法一:SymPy.solve/nsolve函数求解

实际上SymPy内置解方程方法为迭代法。

输入:

import numpy as np
from sympy import *
var("x")
#方法一:求解解析解,进一步得出数值解
#solve()参数:表达式,自变量
result1=solve(x**3-x-1,x)
#求出解析解后,可根据解析解找出所需要的解,evalf()输出数值解
result1[2].evalf()
#方法二:对于没有解析解的方程,直接输出数值解
result2=nsolve(ln((513+0.6651*x)/(513-0.6651*x))-x/(1400*0.0918),0)
result2

输出:

1.32471795724475
0

解法二:迭代法

本方法仅提供一种思路,实际求解时可直接用解法一。实际上SymPy内置解方程方法为迭代法。

示例:双点弦截法

输入:

#解方程法二:迭代法
#方法一:弦截法
import numpy as np
import matplotlib.pyplot as plt
#可以显示中文
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False
# 设置风格
plt.style.use('ggplot')
# 定义函数
init_fun = lambda x: (513+0.6651*x)/(513-0.6651*x)-x/(1400*0.0918)
# 导数
#deri_fun = lambda x: 2*x-4
# input
'''
x0:初始值1
x1:初始值2
theta:误差上界
'''
x0=float(input('输入初始点x0:较大值\n'))
x1=float(input('输入初始点x1:较小值\n'))
theta=1e-7
fig_1 = plt.figure(figsize = (8, 6))
plt.hlines(0,-1,x0,'black','--')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('函数图像')
# 函数图像
x=[]
if x0>0:
    x = np.arange(-1,x0,0.05)
    plt.hlines(0,-1,x0,'black','--')
else:
    x = np.arange(x0,10,0.05)
    plt.hlines(0,x0,10,'black','--')
y = init_fun(x)
def Secant(func = init_fun, x0 =x0, x1 = x1, theta = theta):
    number=0
    while True:
        x2=x0-func(x0)*(x1-x0)/(func(x1)-func(x0))
        plt.vlines(x0,0,init_fun(x0),'blue','--')
        plt.plot([x2,x0],[0,func(x0)],'r--',c='green')
        plt.scatter(x0,func(x0),c='black')
        if abs(func(x2))<theta:
            return x2,number
        x0=x1
        x1=x2
        number += 1
        
# 迭代法计算求解x0
xi,number = Secant(init_fun, x0, x1, theta)
print('迭代结果:'+str(xi))
print('迭代次数:'+str(number))
## 函数求解
plt.plot(x,y)
plt.show()

输出:

输入初始点x0:较大值
300
输入初始点x1:较小值
100
迭代结果:256.84798935747875
迭代次数:5

【Python】常用数值方法的python实现

如果希望在lambda表达式输入对数、指数操作,只需引用np:

# 定义函数
init_fun = lambda x: np.log((513+0.6651*x)/(513-0.6651*x))-x/(1400*0.0918)

求解多元方程组

与求解一元方程同理。

方法一:运用SymPy

输入:

import numpy as np
from sympy import *
var("x,y")
#方法一:求解解析解,进一步得出数值解
#solve()参数:表达式,变量
result1=solve((x**2+x*y+1,y**2+x*y+2),x,y)
result1
#求出解析解后,可根据解析解找出所需要的解,evalf()输出数值解
result1[0][0].evalf()
#方法二:对于没有解析解的方程,直接输出数值解
var("x,y")
f1=x**2+y*x-1
f2=y**2+x+2
#初值要选好,否则会迭代不收敛
nsolve((f1,f2),(x,y),(I,I))

输出:

[(-sqrt(3)*I/3, -2*sqrt(3)*I/3), (sqrt(3)*I/3, 2*sqrt(3)*I/3)]
-0.577350269189626*I
Matrix([
[0.518912794385156 - 0.666609844932019*I],
[ 0.208223290106041 + 1.60070913439255*I]])

方法二:运用SciPy.optimize.fsolve()

输入:

from scipy.optimize import fsolve
from math import sin
def f(x):
    x0,x1,x2=x.tolist()
    return[
        5*x1+3,
        4*x0*x0-2*sin(x1*x2),
        x1*x2-1.5
    ]
#f计算方程组误差,[1,1,1]是初值
result=fsolve(f,[1,1,1])
print(result)
print(f(result))

输出:

[-0.70622057 -0.6        -2.5       ]
[0.0, -9.126033262418787e-14, 5.329070518200751e-15]

解线性方程组

方法:使用scipy.linalg的solve()方法

输入:

import numpy as np
from scipy.linalg import solve
a = np.array([[3, 1, -2], [1, -1, 4], [2, 0, 3]])
b = np.array([5, -2, 2.5])
x = solve(a, b)
x

输出:

array([0.5, 4.5, 0.5])

插值法

方法综述

插值是一种通过已知的离散数据来求未知数据的方法。与拟合不同的是,它要求曲线通过所有的已知数据。SciPy的interpolate模块提供了许多进行插值运算的函数。

问题分类

一元函数插值

其他插值方法的Python实现可参考:http://liao.cpython.org/scipy11/

文档仅演示最常用的B样条插值。

B样条插值

方法一:interp1d()

一维数据的B样条插值运算可以通过interp1d()完成。

interp1d(x,y,kind='cubic')

参数x和y:一系列已知的数据点。
kind:插值类型。

  • 'zero','nearest':阶梯插值,相当于0阶B样条曲线。
  • 'slinear','linear':线性插值,相当于1阶B样条曲线。
  • 'quadratic','cubic':2阶和3阶B样条曲线,更高阶的曲线可直接使用整数值指定。

实际常用三次B样条插值。

输入:(数据来源于本人物理实验BII弗兰克-赫兹实验数据)

#绘图工具:pylab
import numpy as np
from scipy import interpolate
import pylab as pl
x=[15.1,17.1,19.1, 19.4,21.4,23.4, 25.6,27.6,29.6, 30.6,32.6,34.6, 37.0,39.0,41.0, 42.5,44.5,46.5, 49.2,51.2,53.2, 54.7,56.7,58.7, 61.8,63.8,65.8, 67.6,69.6,71.6, 75.2,77.2,79.2, 81.2,83.2,85.2]
y=[175,213,179, 176,87,178, 298,340,272, 171,43,168, 345,407,332, 159,24,158, 399,466,371, 210,57,164, 433,517,437, 250,144,229, 518,588,505, 369,307,379]
xnew=np.arange(15.1,85.2,0.1)
pl.plot(x,y,"ro")
for kind in ["cubic"]:#插值方式
    #"nearest","zero"为阶梯插值
    #slinear 线性插值
    #"quadratic","cubic" 为2阶、3阶B样条曲线插值
    f=interpolate.interp1d(x,y,kind=kind)
    # ‘slinear’, ‘quadratic’ and ‘cubic’ refer to a spline interpolation of first, second or third order)
    ynew=f(xnew)
    pl.plot(xnew,ynew,label=str(kind))
pl.legend(loc="lower right")
pl.show()

输出:
【Python】常用数值方法的python实现

与示波器输出的图像十分类似。

【Python】常用数值方法的python实现

方法二:splrep()+splev()

该函数可以找到一维曲线的B-spline表示。

输入:

import numpy as np
import matplotlib.pyplot as plt
#进行样条插值
import scipy.interpolate as spi
 
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
#数据准备
X=[15.1,17.1,19.1, 19.4,21.4,23.4, 25.6,27.6,29.6, 30.6,32.6,34.6, 37.0,39.0,41.0, 42.5,44.5,46.5, 49.2,51.2,53.2, 54.7,56.7,58.7, 61.8,63.8,65.8, 67.6,69.6,71.6, 75.2,77.2,79.2, 81.2,83.2,85.2]
Y=[175,213,179, 176,87,178, 298,340,272, 171,43,168, 345,407,332, 159,24,158, 399,466,371, 210,57,164, 433,517,437, 250,144,229, 518,588,505, 369,307,379]
#定义插值点
ix3=np.arange(15.1,85.2,0.1)
 
#进行三次样条拟合
ipo3=spi.splrep(X,Y,k=3) #样本点导入,生成参数
iy3=spi.splev(new_x,ipo3) #根据观测点和样条参数,生成插值
#作图
plt.plot(X,Y,'o',ix3,iy3)
plt.xlabel('Vp/V')
plt.ylabel('IA/μA')
plt.title('弗兰克-赫兹图')
plt.show()

输出:
【Python】常用数值方法的python实现

二元函数插值

绘制2D图

输入:

# -*- coding: utf-8 -*-
import numpy as np
from scipy import interpolate
import pylab as pl
import matplotlib as mpl
def func(x, y):
    return (x+y)*np.exp(-5.0*(x**2 + y**2))
# X-Y轴分为15*15的网格
y,x= np.mgrid[-1:1:15j, -1:1:15j]
fvals = func(x,y) # 计算每个网格点上的函数值  15*15的值
print(len(fvals[0]))
#三次样条二维插值
newfunc = interpolate.interp2d(x, y, fvals, kind='cubic')
# 计算100*100的网格上的插值
xnew = np.linspace(-1,1,100)#x
ynew = np.linspace(-1,1,100)#y
fnew = newfunc(xnew, ynew)#仅仅是y值   100*100的值
# 输出解得所求点的插值
print(newfunc(0.01,0.01))
# 绘图
# 为了更明显地比较插值前后的区别,使用关键字参数interpolation='nearest'
# 关闭imshow()内置的插值运算。
pl.subplot(121)
im1=pl.imshow(fvals, extent=[-1,1,-1,1], cmap=mpl.cm.hot, interpolation='nearest', origin="lower")#pl.cm.jet
#extent=[-1,1,-1,1]为x,y范围  favals为
pl.colorbar(im1)
pl.subplot(122)
im2=pl.imshow(fnew, extent=[-1,1,-1,1], cmap=mpl.cm.hot, interpolation='nearest', origin="lower")
pl.colorbar(im2)
pl.show()

输出:

15
[0.01985751]

【Python】常用数值方法的python实现

绘制3D图

输入:

# -*- coding: utf-8 -*-
"""
演示二维插值。
"""
# -*- coding: utf-8 -*-
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib as mpl
from scipy import interpolate
import matplotlib.cm as cm
import matplotlib.pyplot as plt
def func(x, y):
    return (x+y)*np.exp(-5.0*(x**2 + y**2))
# X-Y轴分为20*20的网格
x = np.linspace(-1,1,20)
y = np.linspace(-1,1,20)
x, y = np.meshgrid(x, y)#20*20的网格数据
fvals = func(x,y) # 计算每个网格点上的函数值  15*15的值
fig = plt.figure(figsize=(9, 6))
#Draw sub-graph1
ax=plt.subplot(1, 2, 1,projection = '3d')
surf = ax.plot_surface(x, y, fvals, rstride=2, cstride=2, cmap=cm.coolwarm,linewidth=0.5, antialiased=True)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('f(x, y)')
plt.colorbar(surf, shrink=0.5, aspect=5)#标注
#二维插值
newfunc = interpolate.interp2d(x, y, fvals, kind='cubic')#newfunc为一个函数
# 计算100*100的网格上的插值
xnew = np.linspace(-1,1,100)#x
ynew = np.linspace(-1,1,100)#y
fnew = newfunc(xnew, ynew)#仅仅是y值   100*100的值  np.shape(fnew) is 100*100
xnew, ynew = np.meshgrid(xnew, ynew)
# 输出解得所求点的插值
print(newfunc(0.01,0.01))
# 绘制3D图
ax2=plt.subplot(1, 2, 2,projection = '3d')
surf2 = ax2.plot_surface(xnew, ynew, fnew, rstride=2, cstride=2, cmap=cm.coolwarm,linewidth=0.5, antialiased=True)
ax2.set_xlabel('xnew')
ax2.set_ylabel('ynew')
ax2.set_zlabel('fnew(x, y)')
plt.colorbar(surf2, shrink=0.5, aspect=5)#标注
plt.show()

输出:

[0.01983083]

【Python】常用数值方法的python实现

函数逼近(拟合)

最常用方法:最小二乘拟合

输入:

import numpy as np
from scipy.optimize import leastsq
x=np.array([8.19,2.72,6.39,8.71,4.7,2.66,3.78])
y=np.array([7.01,2.78,6.47,6.71,4.1,4.23,4.05])
def residuals(p):
    "计算以p为参数的直线和原始数据误差"
    k,b=p#若不是线性拟合,则修改对应参数
    return y-(k*x+b)
#leastsq使residuals()输出数组的平方和最小,初值[1,0]
r=leastsq(residuals,[1,0])#若不是线性拟合,则修改对应参数
k,b=r[0]#若不是线性拟合,则修改对应参数
x1=np.arange(2.66,8.71,0.01)
y1=k*x+b#若不是线性拟合,则修改对应参数
print("k=",k,"b=",b)#若不是线性拟合,则修改对应参数
#画图
import matplotlib.pyplot as plt
plt.scatter(x,y,c='g',label='scatter')#散点图
plt.plot(x1,y1,'b--',label='fitting')
plt.title('polyfitting')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()#显示标签
plt.show()

输出:

k= 0.6134953491930442 b= 1.794092543259387

【Python】常用数值方法的python实现

计算误差曲面函数:

def S(k,b):
  #计算直线y=k*x+b与原始数据x,y误差的平方和
  error=np.zeros(k,shape)
  for  x,y in zip(x,y)
    error+=(y-(k*x+b))**2
   return error

若使用其他函数进行拟合,只需将k*x+b替换为对应和函数(参数)即可。
如,三次多项式拟合图像如下:

【Python】常用数值方法的python实现

微分方程数值解法

常微分方程

方法一:SymPy.dsolve()

【Python】常用数值方法的python实现

输入:

import numpy as np
from sympy import *
f = Function('f')
x = symbols('x')
eq = Eq(f(x).diff(x, x) - 2*f(x).diff(x) + f(x), sin(x))
print(dsolve(eq, f(x)))

输出:

Eq(f(x), (C1 + C2*x)*exp(x) + cos(x)/2)

方法二:****scipy.integrate.odeint()

这个函数,要求微分方程必须化为标准形式,即

【Python】常用数值方法的python实现

输入:

import math
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
def func(y, t):
    return t * math.sqrt(y)
YS=odeint(func,y0=1,t=np.arange(0,10.1,0.1))
t=np.arange(0,10.1,0.1)
plt.plot(t, YS, label='odeint')
plt.legend()
plt.show()

输出:
【Python】常用数值方法的python实现

方法三:单个函数四阶龙格-库塔法

输入:

import math
import numpy as np
import matplotlib.pyplot as plt
def runge_kutta(y, x, dx, f):
    """ y is the initial value for y
        x is the initial value for x
        dx is the time step in x
        f is derivative of function y(t)
    """
    k1 = dx * f(y, x)
    k2 = dx * f(y + 0.5 * k1, x + 0.5 * dx)
    k3 = dx * f(y + 0.5 * k2, x + 0.5 * dx)
    k4 = dx * f(y + k3, x + dx)
    return y + (k1 + 2 * k2 + 2 * k3 + k4) / 6.
if __name__=='__main__':
    t = 0.
    y = 1.
    dt = .1
    ys, ts = [], []

def func(y, t):
    return t * math.sqrt(y)

while t <= 10:
    y = runge_kutta(y, t, dt, func)
    t += dt
    ys.append(y)
    ts.append(t)
exact = [(t ** 2 + 4) ** 2 / 16. for t in ts]
plt.plot(ts, ys, label='runge_kutta')
plt.plot(ts, exact, label='exact')
plt.legend()
#plt.show()

输出:
【Python】常用数值方法的python实现

方法四:多个微分方程:欧拉法

输入:

import numpy as np
"""
移动方程:
t时刻的位置P(x,y,z)
steps:dt的大小
sets:相关参数
"""
def move(P, steps, sets):
    x, y, z = P
    sgima, rho, beta = sets
    # 各方向的速度近似
    dx = sgima * (y - x)
    dy = x * (rho - z) - y
    dz = x * y - beta * z
    return [x + dx * steps, y + dy * steps, z + dz * steps]
# 设置sets参数
sets = [10., 28., 3.]
t = np.arange(0, 30, 0.01)
# 位置1:
P0 = [0., 1., 0.]
P = P0
d = []
for v in t:
    P = move(P, 0.01, sets)
    d.append(P)
dnp = np.array(d)
# 位置2:
P02 = [0., 1.01, 0.]
P = P02
d = []
for v in t:
    P = move(P, 0.01, sets)
    d.append(P)
dnp2 = np.array(d)
"""
画图
"""
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
fig = plt.figure()
ax = Axes3D(fig)
ax.plot(dnp[:, 0], dnp[:, 1], dnp[:, 2])
ax.plot(dnp2[:, 0], dnp2[:, 1], dnp2[:, 2])
plt.show()

输出:
【Python】常用数值方法的python实现

偏微分方程

与解常微分方程原理相同。

参考链接

https://zhuanlan.zhihu.com/p/81488678

https://zhuanlan.zhihu.com/p/164627678

上一篇:机器学习实战学习笔记(二)-KNN算法(2)-KNN算法改进约会网站的配对效果


下一篇:梯度下降法+python代码实现解释