Octave简明教程(二)
本篇文档主要内容是Octave编程中的函数定义相关内容,包括可变参数、多返回值、可变返回值等内容。以及通过Octave进行矩阵运算、求解联立方程组、计算矩阵特征值和特征向量等等。另外我会介绍Octave的高阶绘图技巧(包括绘制3D图形)。
Octave 函数
Octave 中的脚本能实现一些简单的程序,但是比脚本更加强大的是用户自定义函数。自定义函数能够让你在命令行、其他函数中和脚本中调用。在 Octave 函数中参数是通过值传递的而不是通过 reference 传递的。Octave的函数允许返回多个返回值。Octave 函数如同脚本一样,是写入一个纯文本文件中的。
无返回值的函数
不同的是函数文件的第一行遵循如下的格式,不过在命令行交互式环境,依然能够支持编写函数:
比如下面比较具体的例子:带参数的函数和不带参数的无返回值函数
1octave:1> function wakeup
2> printf ("hello function\n");
3> endfunction
4octave:2> wakeup()
5hello function
6
7# 带参数的函数
8octave:3> function wakeup (message)
9> printf ("this is my args: %s\n", message);
10> endfunction
11octave:4> what
12M-files in directory /Users/zchanglin/Desktop/OctaveWK:
13
14 draw_sin_cos.m ustep.m
15octave:5> wakeup('hello function with args')
16this is my args: hello function with args
17octave:6>
带返回值的函数
带参数的函数和不带参数的有返回值函数通常遵循如下格式:
1function ret-var = name (arg-list) # 单个返回值
2 body
3endfunction
4
5function [ret1, ret2, ret3] = name (arg-list) # 多个返回值
6 body
7endfunction
来看看具体的例子:
1octave:1> function retval = add (a, b)
2> retval = a + b;
3> endfunction
4octave:2> add(10,20)
5ans = 30
6octave:3> function [ret1,ret2,ret3] = allAddOne(a, b, c)
7> ret1 = a + 1;
8> ret2 = b + 1;
9> ret3 = c + 1;
10> endfunction;
11octave:4> [r1,r2,r3] = allAddOne(10, 20, 30);
12octave:5> r1
13r1 = 11
14octave:6> r2
15r2 = 21
16octave:7> r3
17r3 = 31
在 Octave 中,三角函数都是使用的弧度制,但是有时候使用角度制也比较的方便。但是使用角度制的时候每次都需要使用 $sin(d/180*pi) $将角度 d 转换为弧度制并调用三角函数。但是若是使用一个函数 $sind(d)$ 会让代码更加的简单异读。那么可以创建这么一个函数,创建一个名为sind.m 的文本文件并包含以下内容:
这个函数看似简单,但是很多函数都是这样简单但是很有效。同样的,对于任意一个函数,都可以使用 help 命令阅读关于此函数的说明:
1octave:1> function s=sind(x)
2 % SIND(x) Calculates sine(x) in degrees
3 s=sin(x*pi/180);
4endfunction
5octave:2> help sind
6'sind' is a command-line function
7
8 SIND(x) Calculates sine(x) in degrees
9
10Additional help for built-in functions and operators is
11available in the online version of the manual. Use the command
12'doc <topic>' to search the manual index.
13
14Help and information about Octave is also available on the WWW
15at https://www.octave.org and via the help@octave.org
16mailing list.
17octave:3> sind(30)
18ans = 0.5000
19octave:4> sind(90)
20ans = 1
21octave:5> sind([30 60 90])
22ans =
23
24 0.5000 0.8660 1.0000
可变长度参数
如果特殊参数名称 varargin
出现在函数参数列表的末尾,则表明函数采用了可变数量的输入参数。使用 varargin
函数看起来像如下这样:
varargin
的一个更复杂的示例是一个函数 print_arguments
,它打印所有输入参数:
1octave:1> function print_arguments (varargin)
2> for i = 1:length (varargin)
3> printf ("Input argument %d: ", i);
4> disp (varargin{i});
5> endfor
6> endfunction
7octave:2> print_arguments(1, 5, "Tim")
8Input argument 1: 1
9Input argument 2: 5
10Input argument 3: Tim
11octave:3>
可变长度返回值
可以使用类似于特殊 varargin
参数名称的语法从函数返回可变数量的输出参数。为了让函数返回可变数量的输出参数,使用了特殊的输出参数名称 varargout
。与 varargin
一样, varargout
是一个单元格数组,其中将包含请求的输出参数。
比如下面这个例子,此函数将第一个输出参数设置为1,第二个设置为2,以此类推,返回值随参数列表变化:
1octave:1> function varargout = one_to_n ()
2 for i = 1:nargout
3 varargout{i} = i;
4 endfor
5endfunction
6octave:2> [a, b, c, d] = one_to_n()
7a = 1
8b = 2
9c = 3
10d = 4
11octave:3>
参数/返回值有效性校验
Octave是一种弱类型的编程语言。因此有可能调用一个带有参数的函数,而这些参数可能会导致错误或产生不良的副作用。例如:调用一个带有巨大稀疏矩阵的字符串处理函数。
在函数的头部验证它是否被正确调用是一个很好的做法,Octave 提供了几个用于此目的的函数,不过在看校验函数的使用之前先看看自己如何手动校验的:
1octave:1> function ret = add(x)
2 % Compute the sum of all the elements of the vector
3 tmp = 0;
4 m = length(x);
5 if(m < 2)
6 error("args nums error, must >= 2")
7 endif
8 for n=1:m
9 tmp = tmp + x(n);
10 endfor
11 ret = tmp;
12endfunction
13octave:2> add([0])
14error: args nums error, must >= 2
15error: called from
16 add at line 5 column 9
17octave:3> add([0 3 4 5])
18ans = 12
narginchk
和 nargoutchk
函数提供了类似的错误检查。narginchk(minargs, maxargs)
检查输入参数的数量是否正确,如果调用函数中的参数数量超出 minargs 和 maxargs 范围,则生成错误消息。否则就什么都不做,对于 nargoutchk
也是如此,不过它是用来校验返回值的。
minargs 和 maxargs 必须是标量数值。零、无穷大和负值都是允许的,并且 minargs 和 maxargs 可以相等。
校验参数
1octave:1> function print_arguments (varargin)
2narginchk(1, 3)
3for i = 1:length (varargin)
4 printf ("Input argument %d: ", i);
5 disp (varargin{i});
6endfor
7endfunction
8octave:2> print_arguments(2,3,4,5)
9error: narginchk: too many input arguments
10error: called from
11 narginchk at line 60 column 5
12 print_arguments at line 2 column 1
13octave:3> print_arguments(2,3)
14Input argument 1: 2
15Input argument 2: 3
16octave:7>
校验返回值
1octave:1> function varargout = one_to_n ()
2 nargoutchk(1, 3)
3 for i = 1:nargout
4 varargout{i} = i;
5 endfor
6endfunction
7octave:2> [a b c d] = one_to_n()
8error: nargoutchk: Too many output arguments.
9error: called from
10 nargoutchk at line 102 column 7
11 one_to_n at line 2 column 3
12octave:3> [a, b, c] = one_to_n()
13a = 1
14b = 2
15c = 3
16octave:4>
加载函数操作路径
调用函数时,Octave 会在目录列表中搜索包含函数声明的文件。其实仍然是工作空间的概念,默认情况下会从 Octave 分发的目录列表以及当前工作目录加载所有的函数。addpath
函数可以向加载路径添加目录,rmpath
命令会从加载路径中删除目录,这一点与Script模式一样。
矩阵与向量运算
矩阵乘法
矩阵乘法必需满足一定的矩阵大小规则,在矩阵乘法中,矩阵大小为: $$ (l \times m) *(m \times n) \rightarrow(l \times n) $$ 方阵还可以使用矩阵乘方计算:
1octave:1> A=[1 2; 2 1] # 定义矩阵
2A =
3
4 1 2
5 2 1
6
7octave:2> A * A # 矩阵乘法
8ans =
9
10 5 4
11 4 5
12
13octave:3> A.^2 # 元素逐个2次方
14ans =
15
16 1 4
17 4 1
18
19octave:4> A.*A # 元素逐个相乘
20ans =
21
22 1 4
23 4 1
24
25octave:15>
矩阵转置
矩阵转置运算,将其由行向量转为列向量或者由列向量转换为行向量:
1octave:1> A = rand(2,3) # 生成随机2 * 3的矩阵
2A =
3
4 0.3702 0.1428 0.9297
5 0.3102 0.1288 0.5229
6
7octave:2> A'
8ans =
9
10 0.3702 0.3102
11 0.1428 0.1288
12 0.9297 0.5229
矩阵创建
之前我们已经知道了 ones 和 zeros 这两个创建全 1 或者 全 0 的矩阵,现在来看看其他的矩阵创建函数,Octave 中使用 eye 矩阵来创建单位矩阵:
单位矩阵是对角矩阵的特殊情况,Octave 提供了 diag 函数来创建这样的矩阵,输入参数为该对角矩阵的对角元向量即可得到对应的对角矩阵。该函数如果传入已经存在的对角矩阵,返回对角元素:
1octave:1> diag([2 4 6 6])
2ans =
3
4Diagonal Matrix
5
6 2 0 0 0
7 0 4 0 0
8 0 0 6 0
9 0 0 0 6
10
11octave:2> A = diag(A)
12A =
13
14 2
15 4
16 6
17 6
18octave:3>
如果需要创建一个空矩阵来向其中添加矩阵元。定义这样的矩阵用方括号实现,比如:E = []
如果你需要用一些小矩阵来创建一个复合矩阵,这在 Octave 中也很简单,但是需要注意子矩阵的行列数的匹配:
1octave:1> A=[5 2 2; 0 8 9];
2octave:2> B=[2 0;0 -1;1 0];
3octave:3> comp=[eye(3) B; A zeros(2,2)]
4comp =
5
6 1 0 0 2 0
7 0 1 0 0 -1
8 0 0 1 1 0
9 5 2 2 0 0
10 0 8 9 0 0
11
12octave:4>
提取矩阵元
引用矩阵中的元素与向量中的操作一样,使用括号 ()
即可。所以对于矩阵,需要指定其行号和列号:
1octave:5> A = [1 2 3; 4 5 6; 7 8 9];
2octave:6> A(1,1)
3ans = 1
4octave:7> A(2,3)
5ans = 6
6octave:8> A(3,1)
7ans = 7
8octave:9>
冒号表达式在其中依然适用,其中冒号用来指定元素的的范围,单独使用该符号表示整行或者整列。
1......
2comp =
3
4 1 0 0 2 0
5 0 1 0 0 -1
6 0 0 1 1 0
7 5 2 2 0 0
8 0 8 9 0 0
9
10octave:3> A = [1 2 3; 4 5 6; 7 8 9];
11octave:4> A(3, :)
12ans =
13
14 7 8 9
15
16octave:5> comp(4, 3:5)
17ans =
18
19 2 0 0
20
21octave:6>
矩阵常用函数
函数 | 描述 |
---|---|
eye | 创建一个单位矩阵 |
zeros | 创建一个全0矩阵 |
ones | 创建一个全1矩阵 |
rand | 创建一个随机矩阵 |
diag | 创建一个对角矩阵,或者提取一个矩阵的对角元 |
inv | 求矩阵的逆矩阵 |
det | 求矩阵特征值 |
trace | 求矩阵的迹 |
eig | 求矩阵的特征向量和特征值 |
rank | 求矩阵的秩 |
null | 计算矩阵的零空间的一组基 |
rref | 增广矩阵进行高斯消元法 |
lu | 计算矩阵LU分解 |
qr | 计算矩阵QR分解 |
svd | 计算矩阵的奇异值分解 |
pinv | 计算矩阵的虚反矩阵 |
下面是一些使用示例:
1octave:14> A = rand(4,4)
2A =
3
4 0.710311 0.930717 0.972224 0.507164
5 0.073195 0.358159 0.669564 0.769522
6 0.258537 0.853792 0.718105 0.450334
7 0.423404 0.290824 0.749534 0.022108
8
9octave:16> eig(A)
10ans =
11
12 2.1140 + 0i
13 0.2524 + 0i
14 -0.2789 + 0.3201i
15 -0.2789 - 0.3201i
16
17octave:17> rank(A)
18ans = 4
19octave:18> inv(A)
20ans =
21
22 3.0323 -0.2937 -2.8678 -0.9233
23 -0.1606 -1.0876 2.0792 -0.8121
24 -1.6876 0.5479 0.8557 2.2139
25 1.2547 1.3569 -1.4395 -1.4605
26
27octave:19> rref(A)
28ans =
29
30 1 0 0 0
31 0 1 0 0
32 0 0 1 0
33 0 0 0 1
34
35octave:20> lu(A)
36ans =
37
38 0.7103 0.9307 0.9722 0.5072
39 0.3640 0.5150 0.3642 0.2657
40 0.1030 0.5092 0.3839 0.5819
41 0.5961 -0.5125 0.9291 -0.6847
42
43octave:21>
Octave绘图进阶
Octave 的画图功能不仅仅是画简单的二维直角坐标系下的曲线。通过调用 GNUPLOT,它可以画出条形图,3D 表面,等高线图和极坐标图等等。有关这些内容的细节可以参考 Octave 的帮助系统或者官方文档。
子图(窗口多个图像)
要在同一个窗口中创建多幅图片通过 subplot 命令实现。该命令能够将窗口分离成一系列的子窗口,其基本语法为 subplot(row, columns, select)
,其中 select 参数指定当前绘图在子窗口中的序列。子窗口的序号按照从上到下,从左到右的次序递增。接下来用一个例子来说明 subplot 的应用:
1octave:1> x = linspace(0, 20);
2octave:2> subplot(2, 1, 1)
3octave:3> plot(x, sin(x));
4octave:4> subplot(2, 1, 2)
5octave:5> plot(x, cos(x));
3D绘图
Octave 同样提供了多样的 3D 数据可视化工具。最简单的 3D 绘图实现是 plot3 命令。该命令通过接受输入的 x, y 和 z 轴的数据,绘制绘制函数定义的网格线和等高线,使用参数式的函数:
在 3D 绘图中 xlabel 和 ylabel 照常使用,而新的 zlabel 命令可以为 z 轴命名。 而且在绘制 3D 图时,线条的样式选项也可以使用,这个在之前的2D绘图中有说到,使用鼠标拖动即可选择合适的视角。
绘制形状
Octave提供了非常多的函数来绘制三维几何形状,球体、圆柱体等都有对应的函数,查阅相关文档即可搞清楚函数的使用方式,比如绘制球体和圆锥:
1octave:1> subplot(2, 2, 1)
2octave:2> [x, y, z] = cylinder (10👎0, 50);
3surf (x, y, z);
4title ("a cone");
5octave:3> subplot(2, 2, 2)
6octave:4> [x, y, z] = sphere (40);
7surf (3*x, 3*y, 3*z);
8axis equal;
9title ("sphere of radius 3");
10octave:5> subplot(2, 2, 3)
11octave:6> [x, y, z] = sphere (40);
12surf (1*x, 1*y, 1*z);
13axis equal;
14title ("sphere of radius 1");
15octave:7> subplot(2, 2, 4)
16octave:8> [x, y, z] = sphere (8);
17surf (3*x, 3*y, 3*z);
18axis equal;
19title ("sphere of radius 3");
20octave:9>
绘制曲面
定义一个二元函数 $f(x, y)$,该函数的曲面可以用一系列的 Octave 的工具画出。首先,要初始化一个网格点,用 meshgird 命令实现。
1octave:1> x=2:0.2:4;
2octave:2> y=1:0.2:3;
3octave:3> [X,Y]=meshgrid(x,y);
4octave:4> Z=(X-3).^2-(Y-2).^2;
5octave:5> subplot(2, 2, 1);
6octave:6> surf(Z);title('surf')
7octave:7> subplot(2, 2, 2)
8octave:8> mesh(Z);title('mesh')
9octave:9> subplot(2, 2, 3)
10octave:10> meshz(Z);title('meshz')
11octave:11> subplot(2, 2, 4)
12octave:12> contour(Z);title('contour')
参考资料
Octive官方文档 https://octave.org/doc/v6.4.0/