非线性方程组——牛顿迭代求根

这篇具有很好参考价值的文章主要介绍了非线性方程组——牛顿迭代求根。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


背景

三维曲面函数的参数方程 x(u,v),y(u,v) ,z(u,v),且曲面满足凸性质,u属于[0,1],v属于[0,1],则在xoy平面任给一点p,过点p做一条平行与z轴的直线,假如该直线与曲面相交与一点,求该点的u,v值。用c++程序编写该数学案例,考虑继承和多态,要求用户给定的曲面作为输入,输出为u,v值。

1 案例分析

假设已知点p在曲面上的投影坐标为( x 0 , y 0 , t x_0,y_0,t x0,y0,t
x 0 = x ( u , v ) y 0 = y ( u , v ) x_0 = x(u,v)\\ y_0 = y(u,v) x0=x(u,v)y0=y(u,v)
故令
F : { f = x ( u , v ) − x 0 g = y ( u , v ) − y 0 F:\begin{cases} f = x(u,v) - x_0\\ g = y(u,v)-y_0 \end{cases} F:{f=x(u,v)x0g=y(u,v)y0
则该问题可以转换为求解关于 f , g f,g f,g方程组的解

2 算法实现步骤

由于用户指定函数不清楚为线性方程组还是非线性方程组,因此求解该方程组的根采取最优化方法中的牛顿迭代法求解。

  1. ( u , v ) (u,v) (u,v)的范围,可设置其迭代初值 u 0 = 0.5 u_0 = 0.5 u0=0.5 v 0 = 0.5 v_0 = 0.5 v0=0.5 精度 e s p = 1 0 − 6 esp = 10^{-6} esp=106
  2. 牛顿迭代法公式(多元): x ⃗ k + 1 = x ⃗ k − J − 1 ⋅ F ⃗ \vec x_{k+1} = \vec x_{k} -J^{-1} \cdot \vec F x k+1=x kJ1F  ( J − 1 J^{-1} J1为雅可比矩阵的逆矩阵, x ⃗ k + 1 = ( u k + 1 , v k + 1 ) T \vec x_{k+1} = (u_{k+1},v_{k+1})^T x k+1=(uk+1,vk+1)T, x ⃗ k = ( u k , v k ) T \vec x_{k} = (u_{k},v_{k})^T x k=(uk,vk)T)
  3. 将迭代初值带入依次迭代出 x ⃗ 1 , x ⃗ 2 , x ⃗ 3 , ⋯ ⋯ \vec x_1,\vec x_2,\vec x_3,\cdots\cdots x 1,x 2,x 3⋯⋯
  4. 终止条件 ∣ x k + 1 − x k ∣ \lvert x_{k+1} - x_{k}\rvert xk+1xk< e s p = 1 0 − 6 esp = 10^{-6} esp=106
  5. 将Newton-iteration 算法接口进行封装,生成DLL工程
  6. 用户自己指定曲面,隐式调用DLL工程,调用写的牛顿迭代接口,计算出曲面参数 u , v u,v u,v的值。

3 代码实现

3.1 牛顿迭代API封装

//用户函数基类
//User_Base.h
#pragma once
#include"dll_h.h"

class DLL_API User_Base
{
public:
	virtual double f(const double u, const double v) = 0;
	virtual double g(const double u, const double v) = 0;

};
//用户函数子类
//User_function.h
#pragma once
#include"dll_h.h"
#include"User_Base.h"

class DLL_API User_function:public User_Base
{
public:
	 double f(const double u, const double v);
	 double g(const double u, const double v);

};
//用户定义子类函数的实现
//User_function.cpp
#include"User_function.h"
#include<cmath>

double User_function::f(const double u, const double v)
{
	return pow(u, 3) + 3 * pow(v, 2) - 1;   //此处应该减x0,用1代替
}

double User_function::g(const double u, const double v)
{
	return pow(v, 3) + 3 * pow(u, 2) - 1;   //此处应该减y0,用1代替
}
//创建牛顿迭代类
//newton.h
#pragma once
#include"User_Base.h"
#include"User_function.h"
#include<iostream>
#include <iomanip>
#include"dll_h.h"
using namespace std;

class DLL_API newton
{
public:

	//构造函数
	newton(User_Base* m_user, double m_u0, double m_v0);


	//对f对u求导
	double fu(const double u, const double v);
	//对f对v求导
	double fv(const double u, const double v);
	//对g对u求导
	double gu(const double u, const double v);
	//对g对v求导
	double gv(const double u, const double v);

	//交换初值
	void swap(double& a, double& b);

	//牛顿迭代
	void newton_iteration();

	//析构函数
	~newton();


	double u0;
	double v0;
	User_Base* user;

};
//牛顿迭代实现
//newton.cpp
#define DLL
#include"newton.h"

constexpr double esp = 1e-6; //精度
constexpr auto N = 100;  //迭代最大次数 
constexpr double delta = 0.00001;  //求导delta值

//构造函数
newton::newton(User_Base* m_user, double m_u0, double m_v0)
{
	user = m_user;
	u0 = m_u0;
	v0 = m_v0;
}


//f对u导数
double newton::fu(const double u, const double v)
{
	double num = (user->f(u + delta, v) - user->f(u - delta, v)) / (2 * delta);
	return num;
}
//f对v的导数
double newton::fv(const double u, const double v)
{
	double num = (user->f(u, v + delta) - user->f(u, v - delta)) / (2 * delta);
	return num;
}
//g对u的导数
double newton::gu(const double u, const double v)
{
	double num = (user->g(u + delta, v) - user->g(u - delta, v)) / (2 * delta);
	return num;
}
//g对v的导数
double newton::gv(const double u, const double v)
{
	double num = (user->g(u, v + delta) - user->g(u, v - delta)) / (2 * delta);
	return num;
}

void newton::swap(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}

void newton::newton_iteration()
{
	//user = new User_function();
	//d_user = new d_func();

	double u = 0, v = 0, det = 0;
	double n = 0;//统计迭代次数

	do
	{
		//v[0][0]-fu v[0][1]-fv v[1][0]-gu v[1][1]-gv
		//雅可比行列式
		double det 如果是这样写 那么就每次出这个循环之后det都会被置为0 直接写det 者和上面定义的det是同一个
		det =fu(u0, v0) * gv(u0, v0) - fv(u0, v0) * gu(u0, v0);

		//雅可比矩阵的逆
		double J[2][2] = { gv(u0, v0) / det,-fv(u0, v0) / det,-gu(u0, v0) / det,fu(u0, v0) / det };

		//若det == 0 ,无法计算雅可比矩阵的逆,退出循环
		if (abs(det) < esp) { break; };

		//迭代公式
		u = u0 - (J[0][0] * user->f(u0, v0) + J[0][1] * user->g(u0, v0));
		v = v0 - (J[1][0] * user->f(u0, v0) + J[1][1] * user->g(u0, v0));

		//记录每次迭代的迭代次数
		cout << "迭代次数:" << n++ << std::endl;
		if (n == N) //限定迭代次数,防止一直进行迭代
		{
			break;
		}
		else
		{
			std::cout.unsetf(std::ios::fixed);
			std::cout << std::setprecision(8) << "u = " << u << std::endl;
			std::cout << std::setprecision(8) << "v = " << v << std::endl;
			std::cout << "det:" << det << std::endl;

			//交换u,v值
			swap(u0, u);
			swap(v0, v);
		}

	} while (abs(u - u0) > esp && abs(v - v0) > esp);

	u = u0;
	v = v0;

	std::cout << "-------------------------" << std::endl;
	if (u >= 0 && u <= 1 && v >= 0 && v <= 1 && abs(det) < esp)
	{
		std::cout << "u = " << u << std::endl;
		std::cout << "v = " << v << std::endl;
	}
	else
	{
		std::cout << "没有找到对应的u,v值" << std::endl;
	}

}

newton::~newton()
{
	if (user != NULL)
	{
		delete user;
		user = NULL;
	}
}

//生成DLL时必须宏定义导出
//dll_h.h
#pragma once
//宏定义导出
#ifdef DLL//如果定义了DLL 就定义 DLL_API为 __declspec(dllexport)
#define DLL_API __declspec(dllexport)//导出
#else 
#define DLL_API __declspec(dllimport)//导入
#endif  
//主函数函数调用
#include<iostream>
using namespace std;
#include"User_Base.h"
#include"newton.h"

int main()
{
	User_Base* user = new User_function();
	newton n(user,0.5, 0.5);
	n.newton_iteration();
	user = NULL;

	system("pause");
	return 0;
}



3.2 DLL库创建-隐式调用DLL库

将header文件和lib&&Dll文件放到你要调用的程序下面,在你的vs下面#include相关的头文件如下图所示。

非线性方程组——牛顿迭代求根
非线性方程组——牛顿迭代求根

相关DLL创建和调用教程地址
DLL库创建与隐式调用教程
b站-编写动态,链接库
Visual Studio提示由于找不到dll,无法继续执行代码的问题解决

4 注意事项

  1. 配置解决平台X86/X64都可以(x64对应的是64位的操作系统x86对应的是win32的一个操作系统,我们编译的时候用的一般都是x86),但是这个导出的DLL,用户引用的时候尽量和导出时的DLL平台一致
  2. 隐式调用动态库(DLL)时,需要三个文件分别为以.h .lib .dll为后缀名的文件,其中.h文件为你动态库中的头文件,.lib为符号文件,.dll为函数可执行代码文件。三者具体区别为【1】

非线性方程组——牛顿迭代求根

非线性方程组——牛顿迭代求根
非线性方程组——牛顿迭代求根

  1. 当我们调用我们dll库时,如果“找不到dll”的错误,可以将我们的.lib文件和.dll文件放到调用dll库文件(必须和.exe文件放到一起不然的话也会报错)的debug文件下(因为.exe文件一般都放在debug文件的下面)。
  2. 当我们有太多头文件中的函数需要___declspec(dllexport) 时候,我们可以创建一个类似与此案例中的dll_h.h,然后让每个头文件中都包含dll_h.h,直接对函数或者类进行宏定义。在定义宏定义的时候我们要注意是“导入”还是“导出”,判别方法我们可以将鼠标放到导入和导出的宏定义上面的提示进行判断。在此案例中需要导出的类就只有newton类,所以在定义宏时也只需要在其源文件中首行定义#define DLL就行
//生成DLL时必须宏定义导出
//dll_h.h
#pragma once
//宏定义导出
#ifdef DLL//如果定义了DLL 就定义 DLL_API为 __declspec(dllexport)
#define DLL_API __declspec(dllexport)//导出
#else
#define DLL_API __declspec(dllimport)//导入
#endif  ```
  1. 在本文这种没有提前定义宏的,还需要在源文件重新定义#define DLL,注意必须在源文件的第一行加入#define DLL,因为你用的是ifdef(if define ,如果定义了宏,注意与ifndef的区别)
  2. #ifndef、__declspec(dllexport)、extern “C“的学习(写的非常好)
  3. 主函数中的user指向堆区的内存在dll工程中的析构函数中进行了释放,在主函数中不要对其进行释放,否则重复释放了堆区内存。
    1. C++ 全局变量被自身文件/项目内其他文件/动态链接库(DLL)之外文件使用 ” 一定要特别注意!!!!

5 API说明

接口形式
//初始化
Newton n (1, 2, 3);
1- 用户所创建指向子类的父类指针
2,3 - 牛顿迭代初值

//调用牛顿迭代API
n.newton_iteration();

思考

  1. 利用多态创建函数。由于基类指针指向子类对象不同,最后得出得结果也会不同,这个结果会覆盖我们所写的dll工程中的结果,所有当我们输入的函数不同的时,最后得出的结果也不回相同。如何不利用多态来创建函数,则我们在封装dll时,dll工程中的函数就是固定死的,不管我们如何在我们的exe程序中修改这个函数,最后得出得结果都不会发生变化。
  2. 用牛顿迭代法求解方程组的解的时候,遇到方程组有多个解的时候牛顿法就求解不了,牛顿法只能求解到这个方程组的解离迭代点最近的那个解。对于我这个例子想要用牛顿迭代法求解的话,我们还需要大致判断有几个解,这个数学案例中给了u,v的初始范围为[0,1],大致判断f,g函数的单调性,大致画出f,g函数的函数图像,如果f,g函数有几个交点就对应有几个根。
  3. 一个类的成员函数作为地址进行回调处理的时候,**其实还传入了一个this指针,所有在我们接受这个这个成员函数的地址的时候,我们还需要用引用或者指针的方式来指定是那个对象的this指针。**对于这种回调函数的写法,最好采用函数包装器function来写。
#include <iostream>
#include <functional>//function需要包含头文件
using namespace std;
constexpr auto delta = 0.000000000001;


class User_function
{
public:
	double f(const double x, const double y)
	{
		return x * x * x + 3 * y * y - 3;
	}
};

//fx函数求导;;
class DerivativeX
{
public:

	//初始化构造函数
	//DerivativeX(function< double(User_function*, const double x, const double y)> m_F, User_function user, const double x, const double y)
	DerivativeX(function< double(User_function&, const double x, const double y)> m_F, User_function user, const double x, const double y)
	{
		F = m_F;
		d_user = user;
		//d_user = &user;//指针指向类对象User_function user的地址
		X = x;
		Y = y;
	}

	//F对X求导
	double func()
	{
		double num = (F(d_user, X + delta, Y) - F(d_user, X - delta, Y)) / (2 * delta);
		return num;
	}

	double X;
	double Y;
	User_function d_user;
	//User_function* d_user;
	function< double(User_function&, const double x, const double y)> F;
};

int main()
{
	User_function user;
	//隐藏的this指针用User_function&接收 或者user_function*  
	//function< double(User_function*, const double x, const double y)> m_F = &User_function::f;
	function< double(User_function&, const double x, const double y)> m_F = &User_function::f;
	DerivativeX fu(m_F, user, 0.5, 0.5);
	//m_F-回调函数 user,0.5,0.5 - 回调函数的参数
	fu.func();
	cout << "函数在x = 0.5,y = 0.5的导数为:" << fu.func() << endl;
	system("pause");
	return 0;
}

疑问

为什么我牛顿迭代里面的关于求导的函数对象已经写死了,按理来说调用DLL工程的时候。用户创建函数时,调用这个DLL工程时候,函数的名称也只能为f,g,要是改成其他的了(如:h,j)程序应该运行不了。但是用户自己定义这个函数的时候,这个函数的对象是可以顺便修改的,修改之后得出来的u,v值也还是正确的。希望以后对知识理解更加深刻了之后能够发现其中的问题。

如下面案例,生成DLL工程时,牛顿迭代里面关于求导的函数已经写死,调用的函数对象都是f,g,封装之后用户写函数调用的时候应该也只能写f,g呀!

//f对u导数
double newton::fu(const double u, const double v)
{
	double num = (user->f(u + delta, v) - user->f(u - delta, v)) / (2 * delta);
	return num;
}
//f对v的导数
double newton::fv(const double u, const double v)
{
	double num = (user->f(u, v + delta) - user->f(u, v - delta)) / (2 * delta);
	return num;
}
//g对u的导数
double newton::gu(const double u, const double v)
{
	double num = (user->g(u + delta, v) - user->g(u - delta, v)) / (2 * delta);
	return num;
}
//g对v的导数
double newton::gv(const double u, const double v)
{
	double num = (user->g(u, v + delta) - user->g(u, v - delta)) / (2 * delta);
	return num;
}

但是用户自己定义这个函数的时候,这个函数的对象是可以顺便修改的(函数对象改为了i,g),修改之后得出来的u,v值也还是正确的。

//User_Base.h
#pragma once
class User_Base
{
public:
	virtual double i(const double u, const double v) = 0;
	virtual double g(const double u, const double v) = 0;
};

//User_function.h
#pragma once
#include"User_Base.h"
class User_function :public User_Base
{
public:
	double i(const double u, const double v);
	double g(const double u, const double v);
};

//User_function.cpp
#include"User_function.h"
#include<cmath>

double User_function::i(const double u, const double v)
{
	return pow(u, 4) + 3 * pow(v, 3) - 2;
}
double User_function::g(const double u, const double v)
{
	return pow(v, 4) + 3 * pow(u, 3) - 3;
}

//main().cpp
#include<iostream>
using namespace std;
#include"User_Base.h"
#include"header/newton.h"


int main()
{
	User_Base* user = new User_function;

	newton n(user, 0.5, 0.5);
	n.newton_iteration();

	user = NULL;
	system("pause");
	return 0;
}


希望大家多多三连,你们的鼓励就是对我最大的支持!谢谢文章来源地址https://www.toymoban.com/news/detail-416542.html

到了这里,关于非线性方程组——牛顿迭代求根的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 牛顿-拉普森法求解线性方程组原理及matlab程序

      在多变量微积分和矩阵理论的交叉点是求解非线性代数方程的迭代方法。设是的 n n n 个未知向量 x ,有 F ( x ) = 0 ∈ R n mathbf{F}left( mathbf{x} right) =0in text{R}^{text{n}} F ( x ) = 0 ∈ R n 就是求解 x 的 n n n 个非线性方程组,其中向量函数具有连续导数,并且雅可比矩阵 F x ( x

    2024年02月05日
    浏览(41)
  • 【matlab】解线性方程组的迭代方法

    (一)矩阵 特征 多项式、特征值、特征向量,稀疏矩阵 1、测试函数eig,poly,poly2str format short g A=[1 2 3;4 5 6;7 8 9] A =      1     2     3      4     5     6      7     8     9 p=poly(A) p =             1          -15          -18  -1.7567e-14 poly2str(p,\\\'x\\\')

    2024年02月06日
    浏览(53)
  • <<数值分析>> 第三章线性方程组的迭代解法

            线性方程组的理论求解公式——,在实际应用中面临着两大问题,1是计算过程复杂,2是无法保证算法的稳定性。同时初始数据存在误差,需要寻求能达到精度要求的、操作和计算过程相对简单的求解方法—— 迭代法。    目录 一.迭代法的基本思想 二.基本迭代

    2024年01月25日
    浏览(53)
  • 数值分析——关于非线性方程求根的迭代法

    目录 一、Aitken算法: 二、Steffensen算法: 三、牛顿切线法 四、定端点弦截法 五、动端点弦截法 六、不动点迭代法 七、二分迭代法 Aitken算法是一种加速级数收敛的方法,适用于递推计算和迭代解法,其核心思想是通过利用级数中的部分和之间的线性关系来加速收敛。 对应的迭

    2024年02月03日
    浏览(58)
  • 【数值分析实验】(五)线性方程组的迭代解法(含matlab代码)

            迭代法就是用某种极限过程去逐步逼近线性方程精确解的方法。迭代法具有需要计算机的存储单元较少、程序设计简单、原始系数矩阵在计算过程中始终不变等优点,但存在收敛性及收敛速度问题。 3.1.1 算法过程 3.1.2 代码 3.1.3 计算结果 3.2.1 算法过程 3.2.2 代码

    2024年02月03日
    浏览(47)
  • 【学习笔记】求解线性方程组的G-S迭代法

    matlab中调用上述函数结果显示: 哪里出问题了啊?

    2024年02月10日
    浏览(39)
  • 高等工程数学 —— 第四章 (2)线性方程组的迭代解法和极小化方法

    迭代的一般解法 因此判断迭代是否收敛可以判断谱半径(最大特征值)是否小于1 可见谱半径越小,收敛速度越快,迭代次数越少。 例题: 当 B B B 的两个特征值相同时可使得取最小值。因为有绝对值,所以等式两边同时平方就好了。 Jacobi迭代法 看道例题就好了! 例: 其实

    2024年02月04日
    浏览(45)
  • 数学建模算法(基于matlab和python)之 线性方程组的迭代法(雅可比迭代、高斯-赛德尔迭代)(7/10)

    实验目的及要求: 1、了解各迭代法的基本原理和特点; 2、判断雅克比迭代、高斯-塞德尔迭代对任意初始向量的收敛性; 3、完成雅克比迭代、高斯-塞德尔迭代算法的程序实现。 实验内容: 1、编写雅可比迭代法与高斯-赛德尔迭代法通用子程序,求解下列线性方程组 ,并考

    2024年02月04日
    浏览(51)
  • Matlab | Lab4——用LU 分解法、 Jacobi 迭代、 Gauss-Seidel 迭代 解线性病态方程组(系数矩阵为Hilbert矩阵)

    考虑线性方程组Hx=b,其中H为n阶Hilbert矩阵,即 通过先给 定解 (例如取x的各个分量为1),再计算出右端向量b的办法给出一个精确解已知的问题. (1)分别编写Doolittle LU 分解法、 Jacobi 迭代、 Gauss-Seidel 迭代的一般程序; (2) 取阶数n=6,分别用 LU 分解法、 Jacobi 迭代、 Gauss-S

    2024年02月11日
    浏览(42)
  • 【数值计算方法(黄明游)】解线性代数方程组的迭代法(一):向量、矩阵范数与谱半径【理论到程序】

       注意:速读可直接跳转至“4、知识点总结”及“5、计算例题”部分   当涉及到线性代数和矩阵理论时, 向量、矩阵范数以及谱半径 是非常重要的概念,下面将详细介绍这些内容: a. 定义及性质   考虑一个 n n n 维向量 x x x ,定义一个实值函数 N ( x ) N(x) N ( x ) ,

    2024年01月25日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包