Octave 是一款开源的数值计算软件. 它所采用动态编程语言和许多库函数与 MATLAB 兼容, 因而在一定程度上可以用作后者的替代品.
最符合开源精神的安装方式是从源代码编译. 如果想跳过编译选项设置及依赖性检查, 可以直接下载各种操作系统下已经编译好的二进制安装文件.
在操作系统的 Shell 中输入 octave, 即可进入 Octave 命令窗口, 使用方式与 MATLAB CLI 非常类似:
A = eye(3);
b = ones(3,1);
x = A \ b;
A*x - b
行末不加 ';' 则下一行输出该表达式的计算结果:
ans =
0
0
0
CLI 常用命令:
| 命令 | 功能 |
|---|---|
clc | 清屏 |
clear | 清除当前工作空间 |
exit 或 quit | 退出 Octave |
Octave GUI 的结构及各部分的名称与 MATLAB GUI 几乎完全一样.
学习和使用 Octave 最可靠最完整的参考文档是 GNU Octave 手册, 可以在线查阅 HTML 或下载 PDF.
如果已经在本地主机安装了 Octave 软件, 可以通过在 Octave 命令提示符后输入 doc 打开 GNU Octave 手册. 如果要查询某一条命令 (例如 rand) 的帮助, 可以在 Octave 命令提示符后输入以下两种命令之一:
help rand
doc rand
区别是:
help 会将所有信息输出, 并立即回到 Octave 命令窗口.doc 会停留在帮助文档中, 以便上下翻阅或查找关键词, 直到按下 Q 才回到 Octave 命令窗口.| 类型 | 实现方式 | Bytes | 最小值 | 最大值 |
|---|---|---|---|---|
logical | logical | 1 | ||
char | char8 | 1 | 0 | 255 |
int | int32 | 4 | intmin | intmax |
double | double | 8 | realmin | realmax |
complex | pair<double,double> | 16 |
表示逻辑判断的结果, 只有 true 和 false 两个值. 尽管只需要 1 bit, 但仍占据 1 byte.
用 ASCII 码表示字符, 可以看做短整型.
Octave 中的整数, 既非数学中的整数, 也不完全等价于 C/C++ 中的 int32. 以下反常的结果可以说明这一问题:
a = intmin
a - 1 == a
a * 2 == a
Octave 中的实数, 不是数学中的实数, 但完全等价于 C/C++ 中的 ` double`, 即 IEEE 754 双精度浮点数. 有几个特殊实数需要特别注意:
| 符号 | 含义 | 来源 |
|---|---|---|
Inf 或 inf | 无穷大 | 分母为零, 分子不为零 |
NaN | Not a Number | 不定式, 如 0/0 |
NA | Not Available | NA 命令 |
Octave 中的复数表示为一对实数, 可以通过在整数或实数常量后紧跟 i 或 j 来创建, 或利用 complex 函数来构造.
c = 0 + 1i
d = 0 + 1j
c*c - d^2 % == 0
Octave 中的矩阵是一种同构 (元素类型相同) 的多维数组, 支持矩阵的加法和乘法运算, 能够动态地扩展或删减.
手动生成矩阵:
% ',' 或 ' ' 表示间隔, 可以省略
% ';' 表示换行, 不可省略
a = [1,2; 3,4]
b = [1 2; 3 4]
a - b
自动生成等距数组:
% start : step : stop
a = 0 : 10
b = 0 : 0.1 : 1
自动生成特殊矩阵 (若只给一个输入参数, 则输出为方阵):
| 函数 | 功能 |
|---|---|
eye(m, n) | 主对角线元素为 1.0, 其余元素为 0.0 |
ones(m, n) | 所有元素为 1.0 |
rand(m, n) | 元素为取自 (0.0, 1.0) 的随机数 |
zeros(m, n) | 所有元素为 0.0 |
a = magic(3)
a(2, 2) % 指定行列的元素
a(2, :) % 第二行所有元素
a(:, 2) % 第二列所有元素
% 当做一维数组访问:
a(4:6) % column-major
a(:)
在大多数语言中, 访问数组的越界成员是非法的, 但在 Octave 中规定如下:
a = [1 2 3 4]
a(8) = 8 % 自动扩张
a(10) % 报错
a = complex(magic(3), ones(3))
a.' % 转置
a' % 共轭转置
a = [1,2; 3,4]
b = [5 6; 7 8]
c = [a, b]
d = [a; b]
字符串可以看做是由字符元素构成的数组.
异构数组.
类似于 C/C++ 中的 struct.
类似于 Java 中的 Map 或 Python 中的 dict.
获取对象的类型信息:
| 函数 | 功能 |
|---|---|
class(a) | 返回 a 的类型 |
isa(a, "type") | 判断 a 是否为 "type" 所表示的类型 |
isnumeric(a) | 判断 a 是否为数值型 (整型, 浮点型) |
ismatrix(a) | 判断 a 是否为矩阵 (二维数组) |
ishermitian(a) | 判断 a 是否为共轭对称矩阵 |
以 is 开头的判断函数被称作谓词 (predicate), 完整列表 (个别函数在 MATLAB 中没有定义) 参见 GNU Octave 手册的 4.8 Predicates for Numeric Objects.
获取对象的大小信息:
| 函数 | 功能 |
|---|---|
length(a) | 第一维有多少个元素 |
ndims(a) | number of dimensions |
numel(a) | number of elements |
size(a) | 几行几列 |
sizeof(a) | 多少字节 |
* 的优先级高于 +, 所以 a + b * c 应解释为 a + (b * c).
运算符优先级混淆会导致程序的语法正确而算法逻辑出错. 当不确定时, 一律主动加上括号, 这样可以从根本上避免此类错误.
在 Octave 中, 各运算符 (自增自减运算符和复合赋值运算符在 MATLAB 中没有定义) 的优先级从高到低 (同组优先级相同) 依次为:
| 优先级 | 运算符 | 示例 |
|---|---|---|
| 1 | 函数调用 | f(x) |
| 1 | 数组访问 | a(1) |
| 1 | 异构数组访问 | a{1} |
| 1 | 结构体成员访问 | point.x |
| 2 | 后置自增, 后置自减 | a++, a-- |
| 3 | 矩阵转置 | a', a.' |
| 3 | 矩阵幂 | a^2, a**2 |
| 3 | 矩阵成员幂 | a.^2, a.**2 |
| 4 | 一元加, 一元减 | +a, -a |
| 4 | 前置自增, 前置自减 | ++a, --a |
| 4 | 逻辑取反 | ~a, !a |
| 5 | 矩阵乘 | a * b, a .* b |
| 5 | 矩阵左除 | a / b, a ./ b |
| 5 | 矩阵右除 | a \ b, a .\ b |
| 6 | 二元加, 二元减 | +a, -a |
| 7 | 序列生成 | 1 : 10 |
| 8 | 矩阵成员与 | a & b |
| 9 | 矩阵成员或 | a | b |
| 10 | 逻辑与 | a && b |
| 11 | 逻辑或 | a || b |
| 12 | 赋值, 复合赋值 | a = 2, a += 2 |
所有控制语句都以关键词 (如 if, switch, while, for) 开头, 以 end 结尾. 在 Octave 中, end 可以加上关键词, 如 endif, endswitch 等, 但这样的代码在 MATLAB 中将会报错.
x = rand();
if x > 0.5
disp('large');
else
disp('small');
end
x = rand();
if x > 0.67
disp('large');
elseif x > 0.33 % 不能写成 else if
disp('medium');
else
disp('small');
end
x = randi(10)
switch x
case 1
disp('x == 1');
case 2
disp('x == 2');
case {3, 4, 5}
disp('x == 3 or 4 or 5');
otherwise
disp('x > 5');
end
其中任何一个 case 后面的代码被执行完后, 控制流直接跳转到 end, 这一点与 C/C++ 中的 switch 不同.
寻找矩阵中的最大成员:
a = rand(1, 5)
x = a(1);
n = numel(a);
for k = 1 : n
if a(k) > x
x = a(k);
end
end
disp(sprintf('max(a) == %f', x));
或
a = rand(1, 5)
x = a(1);
k = 1;
while k <= numel(a)
if a(k) > x
x = a(k);
end
k = k + 1;
end
disp(sprintf('max(a) == %f', x));
有两种方式可以跳过最内层循环的剩余部分:
break 语句用于跳出最内层循环.continue 语句用于跳转到最内层循环的下一次迭代.函数定义的基本形式为:
function [return_list] = name(input_list)
% algorithms
end
其中输入和输出列表可以有零个或多个参数, 例如:
function y = f()
% ...
end
function g(x, y)
% ...
end
function [u, v] = h(x, y)
% ...
end
将函数定义放置在与之同名的文本文件中, 扩展名为 .m, 就创建了一个函数文件. 一个函数文件中有且仅有一个主函数, 但可以定义多个子函数:
% 文件名为 f.m
function f()
disp('in f, calling g');
g()
end
function g()
disp('in g, calling h');
h()
end
function h()
disp('in h');
end
其中 f 为文件外部可见的主函数, g 和 h 为文件外部不可见但内部可见的子函数.
要让函数文件所定义的函数能够被 Octave 调用, 必须将该函数文件放置在 Octave 的搜索路径中. 可以通过 addpath 函数来添加搜索路径:
addpath("~/Desktop")
脚本文件 (script) 与函数文件类似, 也是含有一系列 Octave 命令的 .m 文件, 但没有用关键词 function 与 end 对代码进行封装. 脚本文件中的变量位于脚本调用语句所在的作用域中.
假设在当前工作目录下创建了一个 fib.m 文件, 其中定义了一个用递归的方法计算 Fibonacci 数的函数 fib(n)
function result = fib(n)
n = round(n);
assert(n >= 0)
switch n
case {0, 1}
result = 1;
otherwise
result = fib(n-1) + fib(n-2);
end
end
利用 tic-toc, 可以测量函数运行时间:
tic; fib(20); t = toc
利用 profile, 可以更精细地测量函数运行时间及调用次数等信息:
% Octave 与 MATLAB 均可用
profile clear;
profile on;
fib(20);
profile off;
p = profile('info');
查看测量结果的命令为:
profshow(p, 10); % 仅 Octave 可用
profile viewer; % 仅 MATLAB 可用
Octave 中, 函数参数的默认传递方式是按值传递 (pass by value), 即被传递的是值, 而不是变量 (的地址):
foo = "bar"; % 变量 foo 的值为 "bar"
f(foo) % 被传递的是 "bar" 这个值, 而不是 foo 这个变量
按值传递是 Octave 所保证的语义 (semantics), 但该语义不一定是通过创建局部副本 (复制) 来实现的. 如果函数体中没有修改传入参数的值, 则不必进行复制, 例如:
% a_unchanged.m
function
end
% a_changed.m
function a_changed(a)
a(1, 1) = 1;
end
% Command Window
clear
a = rand(1000); % 8000000 Bytes
profile clear
profile on
for i = 1 : 100
a_unchanged(a); % no copy, very fast
a_changed(a); % do copy, very slow
end
profile off
p = profile('info');
profshow(p, 10); % 仅 Octave 可用
profile viewer; % 仅 MATLAB 可用
按值传递语义的一个重要推论是, 函数体内无法直接修改外部变量的值:
% f.m
function f(a)
a(1) = 0; % 与外部的 a 不是同一个对象
a
end
% Command Window
a = [1 1]
f(a) % 显示 [0 1]
a % 显示 [1 1]
这里有两个名为 a 的变量, 它们都是局部 (local) 变量 (与全局 (global) 变量对应), 但是具有不同的作用域 (scope). 作用域是一种数据保护机制, 可以保证外部环境不被局部变量污染. 在 Octave GUI 的工作空间 (workspace) 窗口中可以查看当前作用域中的局部变量.
全局变量可以绕过作用域规则, 但几乎总是意味着糟糕的设计. 应当避免使用全局变量.
类似于函数 C 中的函数指针, 通过 @ + 函数名:
f = @sin
f(pi) % 像函数一样调用
quad(f, 0, pi/2) % 像变量一样传递
类似于函数 Python 中的 lambda 表达式, 例如:
quad( @cos , 0, pi/2) % 传入函数句柄
quad(@(x) cos(x), 0, pi/2) % 传入匿名函数
二者运行结果相同, 但匿名函数效率很低.
| 函数 | 功能 |
|---|---|
plot(x, y) | 二维点及连线 |
semilogx(x, y) | 同上, 但 x 按对数缩放 |
loglog(x, y) | 同上, 但 x 和y 都按对数缩放 |
contour(x, y, z) | 二维等值线图 |
contourf(x, y, z) | 同上, 但有填充 |
x = -3 : 0.2 : 3;
y = x;
[X, Y] = meshgrid(x, y);
Z = peaks(X, Y);
contour(X, Y, Z);
| 函数 | 功能 |
|---|---|
plot3(x, y, x) | 三维点及连线 |
contour3(x, y, z) | 三维等值线图 |
mesh(x, y, z) | 网格 |
surf(x, y, z) | 曲面 |
| 函数 | 功能 |
|---|---|
title | 标题 |
xlabel | 坐标轴 |
legend | 图例 |
x = 0 : 0.1 : 10;
hold
plot(x, sin(x));
plot(x, cos(x));
title('Trigonometric Functions');
xlabel('x');
ylabel('y');
legend('sin(x)', 'cos(x)');
| 函数 | 功能 |
|---|---|
clf | 清屏 |
pause(0.5) | 暂停 0.5 秒 |
x = [0, 2, 2, 0, 0];
y = [0, 0, 2, 2, 0];
cx = 1;
cy = 1;
dt = 0.02
for t = 0 : dt : 2
clf
hold on
axis equal
axis off
plot(x, y);
a = 8 * t;
r = 0.5 * t;
plot(cx + r*cos(a), cy + r*sin(a), 'b*');
pause(dt);
end
在宏观尺度下, 气体可以视为连续介质; 但在微观尺度下, 气体应当看作是由许多分子组成的系统, 这些分子无时无刻不在做无规则热运动. 描述气体分子热运动规律的理论称为气体动理学理论 (The Kinetic Theory of Gases), 其二维简化版本可以概括为以下几点:
尽管该模型引入了很多简化假设, 但仍然保留了气体分子运动规律最本质的特征. 从该模型出发, 运用统计学工具, 可以导出许多宏观物理量的定义, 从而过渡到连续介质力学的范畴.
考虑一个二维区间 0 < x < 1, 0 < y < 1, 四周为固壁边界. 给定 n 个分子的半径 r(i) 和质量 m(i), 以及 t = 0 时刻的位置 x(i), y(i) 和速度 u(i), v(i). 计算 t > 0 时所有分子的位置和速度, 并用动画 (每隔 0.03 秒画一帧) 实时显示计算结果, 直到按下 [Ctrl] + C.