Octave简明教程(二)

本篇文档主要内容是Octave编程中的函数定义相关内容,包括可变参数、多返回值、可变返回值等内容。以及通过Octave进行矩阵运算、求解联立方程组、计算矩阵特征值和特征向量等等。另外我会介绍Octave的高阶绘图技巧(包括绘制3D图形)。

Octave 函数

Octave 中的脚本能实现一些简单的程序,但是比脚本更加强大的是用户自定义函数。自定义函数能够让你在命令行、其他函数中和脚本中调用。在 Octave 函数中参数是通过值传递的而不是通过 reference 传递的。Octave的函数允许返回多个返回值。Octave 函数如同脚本一样,是写入一个纯文本文件中的。

无返回值的函数

不同的是函数文件的第一行遵循如下的格式,不过在命令行交互式环境,依然能够支持编写函数:

1function name (arg-list)
2  body
3endfunction

比如下面比较具体的例子:带参数的函数和不带参数的无返回值函数

 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 的文本文件并包含以下内容:

1function s=sind(x)
2  % SIND(x) Calculates sine(x) in degrees
3  s=sin(x*pi/180);
4endfunction

这个函数看似简单,但是很多函数都是这样简单但是很有效。同样的,对于任意一个函数,都可以使用 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 函数看起来像如下这样:

1function val = smallest (varargin)
2  body
3endfunction

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

narginchknargoutchk 函数提供了类似的错误检查。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 矩阵来创建单位矩阵:

1octave:1> eye(3)
2ans =
3
4Diagonal Matrix
5
6   1   0   0
7   0   1   0
8   0   0   1

单位矩阵是对角矩阵的特殊情况,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));

分别画sin与cos函数曲线

3D绘图

Octave 同样提供了多样的 3D 数据可视化工具。最简单的 3D 绘图实现是 plot3 命令。该命令通过接受输入的 x, y 和 z 轴的数据,绘制绘制函数定义的网格线和等高线,使用参数式的函数:

1octave:1> f = @(x,y) sqrt (abs (x .* y)) ./ (1 + x.^2 + y.^2);
2octave:2> ezmeshc (f, [-3, 3])

在 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/