数值计算之解方程
- highflybird's Blog
- 登录或注册以发表评论
此处用比较纯LISP的方法来解非线性方程。
主要采用Ridders方法和Van Wijingaarden-Dekker-Brent方法求解。
下面是其源码:
;;;********************************************* ;;; 用Ridders方法求解一元方程的根 ;;;********************************************* (defun ZRidder (func x1 x2 n / ans EPS FH FL FM FNEW ITR J S TOL XH XL XM XNEW XXX b) (setq tol (expt 0.1 n)) (setq itr 1000) (setq EPS 1e-16) (setq xxx -1.11e100) (setq fl (func x1)) (setq fh (func x2)) (if (or (and (> fl 0.0) (< fh 0.0)) (and (< fl 0.0) (> fh 0.0))) (progn (setq xl x1) (setq xh x2) (setq ans xxx) (setq j 0) (setq b T) (while (and b (< j itr)) (setq xm (* 0.5 (+ xl xh))) (setq fm (func xm)) (setq s (sqrt (- (* fm fm) (* fl fh)))) (if (equal s 0 eps) (setq b nil) (progn (setq xnew (+ xm (/ (* (- xm xl) (if (>= fl fh) 1 -1) fm) s))) (if (equal xnew ans tol) (setq b nil) (progn (setq ans xnew) (setq fnew (func ans)) (if (equal fnew 0.0 tol) (setq b nil) (progn (if (/= (solve:sign fm fnew) fm) (setq xl xm fl fm xh ans fh fnew ) (if (/= (solve:sign fl fnew) fl) (setq xh ans fh fnew ) (if (/= (solve:sign fh fnew) fh) (setq xl ans fl fnew ) (princ "\nNever get here!") ) ) ) (if (equal xh xl tol) (setq b nil) ) ) ) ) ) ) ) (setq j (1+ j)) ) ;(princ "\nIterations: ") ;(princ j) (if (>= j itr) (princ "\nzriddr exceed maximum iterations.") ) ) (progn (if (equal fl 0 tol) (setq ans x1) (if (equal fh 0 tol) (setq ans x2) (progn (setq ans (zbrent func x1 x2 n)) ;(setq ans 0) ;(princ "\nroot must be bracketed in zriddr.") ) ) ) ) ) ans ) ;;;********************************************* ;;; 用Van Wijingaarden-Dekker-Brent方法求根 ;;;********************************************* (defun zbrent (func x1 x2 n / a b c d e i p q r s xm fa fb fc EPS Itr tol tol1 min1 min2) (setq tol (expt 0.1 n)) ;容差 (setq Itr 10000) ;最大迭代次数 (setq EPS 1e-16) ;计算机有效精度 (if (> x1 x2) ;如果下届大于上届 (setq a x2 b x1 c x1 ) ;则交换上下界 (setq a x1 b x2 c x2 ) ) (setq fa (func a)) (setq fb (func b)) (if (or (and (> fa 0) (> fb 0)) (and (< fa 0) (< fb 0))) ;上下界函数值同号 (princ "\nRoot must be bracketed in zbrent!") ) (setq fc fb) (setq i 1) (while (<= i Itr) (if (or (and (> fb 0) (> fc 0)) (and (< fb 0) (< fc 0)) ) (setq c a fc fa d (- b a) e d ) ;对a,b,c更名并调整解区间d ) (if (< (abs fc) (abs fb)) (setq a b b c c a fa fb fb fc fc fa ) ) (setq tol1 (+ (* 2.0 EPS (abs b)) (* 0.5 tol))) ;收敛性检查 (setq xm (* 0.5 (- c b))) (if (or (<= (abs xm) tol1) (equal fb 0 1e-16)) ;跳出循环 (setq i Itr) (progn (if (and (>= (abs e) tol1) (> (abs fa) (abs fb)) ) (progn (setq s (/ fb fa)) ;将进行第二次反插 (if (equal a c 1e-16) (setq p (* 2.0 xm s) q (- 1.0 s) ) (setq q (/ fa fc) r (/ fb fc) p (* s (- (* 2.0 xm q (- q r)) (* (- b a) (1- r)))) q (* (1- q) (1- r) (1- s)) ) ) (if (> p 0) (setq q (- q)) ) ;检查是否在解区间内 (setq p (abs p)) (setq min1 (- (* 3.0 xm q) (abs (* tol1 q)))) (setq min2 (abs (* e q))) (if (< (* 2.0 p) (if (< min1 min2) min1 min2 ) ) (setq e d d (/ p q) ) ;可以进行内插 (setq d xm e d ) ;不能进行内插,改用二分法 ) ) (setq d xm e d ) ;届下降速度太慢,改用二分法 ) (setq a b) ;将最新估值赋给a (setq fa fb) (if (> (abs d) tol1) ;计算新的实验解 (setq b (+ b d)) (setq b (+ b (if (>= xm 0) (abs tol1) (- (abs tol1)) ) ) ) ) (setq fb (func b)) ) ) (setq i (1+ i)) ) b )
下面的代码包含了一个应用及其对话框例子:
(vl-load-com) ;;;主程序 (prompt "请输入solve命令!") (defun C:solve (/ f x1 x2 n id Dcl_File) (setq id (load_dialog (setq Dcl_File (Write_Dcl)))) ;装入对话框 (vl-file-delete Dcl_File) ;删除临时文件 (if (new_dialog "dcl_solve" id) (progn (action_tile "EXP" "(SOLVE:GetFunc (setq f $value))") ;从对话框中得到表达式 (action_tile "MIN" "(setq x1 $value)") ;从对话框中得到下届 (action_tile "MAX" "(setq x2 $value)") ;从对话框中得到上届 (action_tile "PRC" "(setq n $value)") ;从对话框中得到精度 (action_tile "ANS" "(GetAnswer f x1 x2 n)") ;求解 (action_tile "help" "(choose 1)") ;帮助 (start_dialog) (unload_dialog id) (princ (GetAnswer f x1 x2 n)) ) ) (princ) ) (defun SOLVE:GetFunc (f) (CAL:Expr2Func f 'func '(x)) ) ;;;********************************************* ;;;从对话框得到方程参数并求解和显示结果 ;;;********************************************* (defun GetAnswer (f x1 x2 n / str1 str2 str3 str4 str5 ret test fra_x int_x result func) (if (and x1 x2 n f) (progn (setq x1 (atof x1)) (setq x2 (atof x2)) (setq n (abs (atoi n))) (if (> n 20) (setq n 20) ) ;;表达式求值 (SOLVE:GetFunc f) ;(setq ret (vl-catch-all-apply 'zbrent (list func x1 x2 n))) (setq ret (vl-catch-all-apply 'ZRidder (list func x1 x2 n))) (if (vl-catch-all-error-p ret) (setq str5 (strcat "出错: " (vl-catch-all-error-message ret))) (progn (setq test (func ret)) (setq fra_x (rtos (- (abs ret) (fix (abs ret))) 2 n)) (setq fra_x (vl-string-left-trim "0" fra_x)) (setq int_x (itoa (fix ret))) (if (equal test 0 1) (setq result (strcat "方程" f "=0" "的解为:" (rtos ret 2 n)) result (list result (rtos ret 2 n)) ) (setq result (strcat "方程" f "=0" "在此区间无解") result (list result (rtos ret 2 n)) ) ) (setq str1 (car result) str2 "\n下面是求解验证结果:" str3 (cadr result) str4 (rtos test 2 n) str5 (strcat str1 str2 "\nf(" str3 ")=" str4) ) ) ) (set_tile "error" str5) str5 ) (set_tile "error" "输入有误!") ) ) (defun Solve:Sign (x y) (if (>= y 0) (if (>= x 0) x (- x) ) (if (>= x 0) (- x) x ) ) ) (defun Sign (x) (if (> x 0) 1.0 (if (zerop x) 0.0 -1.0 ) ) ) ;;;帮助说明函数 (defun choose (n) (if (= n 1) (alert "方程式只接受x(小写)为变量,不规范很可能出错! \n无须写等式,例如求解x^2-2=0写作x^2-2. \n程序采用brent方法求解,不保证每个方程都有效! \n有什么问题email: highflybird@qq.com" ) (set_tile "error" "方程式只接受x(小写)为变量,无须写等式,例如求解x^2-2=0写作x^2-2." ) ) ) ;;; for DCL (defun Write_Dcl (/ Dcl_File file str) (setq Dcl_File (vl-filename-mktemp nil nil ".DCL")) (setq file (open Dcl_File "w")) (princ "dcl_solve : dialog { label = \"一元方程求解程序\"; : boxed_column { : edit_box { key=\"EXP\"; label= \"一元方程:\"; } : row { : edit_box { key=\"MIN\"; label= \"区间下届:\"; edit_width = 8; } : edit_box { key=\"MAX\"; label= \"区间上届:\"; edit_width = 8; } : edit_box { key=\"PRC\"; label= \"精确位数:\"; edit_width = 2; } } spacer_1; } : row { : button { key=\"ANS\"; label=\"求解\"; } ok_cancel_help; } errtile; }" file ) (close file) Dcl_File )