谁是“一等公民”

OpenSees 是一个用于结构分析的软件。类似的软件还有 Sap2000,ABAQUS 等。但是,从使用方法上看,它们又有很大的不同。

Sap2000 与我们通常使用的桌面软件类似,用户主要的操作是通过鼠标在图形化的对话窗口中完成的。其图形界面是“一等公民”。但是,尽管图形界面对于 Sap2000 来说是“一等公民”,它还公开了一系列 API (Application Programming Interface),给用户通过编写程序来调用。用户可以用 VBA、.NET、matlab 等多种语言来写这个程序,但本质上还是调用统一的 Windows 接口。

ABAQUS 的情况则有所不同。虽然我们经常使用的 ABAQUS CAE 也是基于图形界面的,但是 ABAQUS 的“一等公民”是其 inp 文件。 inp 文件是一个文本文件, ABAQUS 核心中的计算模块可以理解其语法,并完成结构的分析。它可以直接编写,类似于写程序,也可以通过 CAE 图形界面来自动生成。而 CAE 模块又是通过 Python 来实现的。也就是说,用户通过鼠标和键盘操作,生成 python 命令,python 命令再生成 inp 文件,再通过 inp 文件来调用计算核心。

回到 OpenSees 。在 OpenSees 中,程序是它的“一等公民”。甚至, OpenSees 并没有提供一个统一的图形化交互界面。因此,使用 OpenSees 的方法就是通过程序。那么 ABAQUS 的程序语言是 inp 文件,OpenSees 的程序语言又是什么呢?这里就涉及到了 OpenSees 解释器。目前, OpenSees 的解释器有基于 TCL 的解释器和 python 解释器两种。

编译型语言和解释型语言

当我们写程序的时候,使用的语言其实有两种。一种是编译型语言,一种是解释型语言。编译型语言是提前将人类可读的语言翻译成机器可读的二进制语言。也就是经历了编译(编辑和翻译)的过程,生成一个可执行的二进制程序。C++、Pascal 等属于编译型语言。而解释型语言是不会生成可执行二进制程序的,而是在执行的过程中,逐条读取人类可读的语言,再逐条翻译成机器语言执行。Python、TCL、Javascript 等属于解释型语言。

编译型语言的优点是执行速度快,因为执行时直接通过二进制语言完成。但是对于不同的平台,需要进行不同的编译,可移植性较差。而解释型语言的优点则是可移植性好,但是执行速度受限。

编译型语言通常采用静态变量类型,其可维护性好,适合于大型项目。而解释型语言通常采用动态变量类型,其灵活性好,更适用于快速开发。

当然,编译型语言和解释型语言也在互相接受对方的优点,以弥补短板。但是其基本的特点并没有改变。

OpenSees 是用 C++ 实现的

OpenSees 的核心代码由 C++ 写成,开源于 GitHub 上。使用编译型语言,使 OpenSees 可以在多所研究机构之间合作编写,方便维护,而且运行的效率比较高。

作为一个软件,需要与用户交互,来完成其功能。在这一点上, OpenSees 选择了通过解释器与用户交互。之所以不使用图形界面,是因为作者相信,编程语言可以更加高效地完成任务。的确,相比于图形界面,编程语言的变量、条件判断和循环等特点可以提高效率。但是在模型的可视化上,又有诸多不便。因此,有很多第三方开发了各种各样的可视化辅助系统。可以在 GitHub 的 Awesome-OpenSees 中找到。

习惯使用图形界面的用户可能会感到使用程序建模不太方便,而是希望通过在其它软件中建模,并导入 OpenSees 。目前也有一些相应的程序满足这一要求,也可以在 Awesome-OpenSees 中找到。但是,由于 OpenSees 在不断更新,有些调用方法发生了改变,有些第三方程序更新得不及时,可能出现报错。所以用户即使使用这种程序,还是需要具备检查其正确性的能力。

当然,作为 C++ 实现的语言,OpenSees 也支持不通过解释器,而是编写 C++ 代码来调用。这可以大大加快运行效率。感兴趣的读者可以跳至XXX。

下面就 TCL 和 Python 分别介绍解释器的特点。

OpenSees 的 TCL 解释器

TCL (Tool Command Language) 的特点非常明显。首先,它的代码紧凑,语法简单,跨平台性好;其次,它的扩展性强,可以用 C++ 进行扩展。正因如此, TCL 被广泛用作 C++ 程序的测试语言。

OpenSees 正是使用 C++ 对 TCL 进行的扩展。也就是说, OpenSees 在 TCL 语言的基础上,扩展了一些命令,使得到的扩展集具有了结构分析的功能。比如说, TCL 本身没有 model 这一命令,而 OpenSees 为它加入了这一命令。这样做的好处是,可以充分利用 TCL 语言自身的语法,进行循环、条件判断、文件IO等操作,而不用开发一种新的语言(如 ABAQUS 的 inp)。使用户更容易上手。

因此, OpenSees 的 TCL 解释器并不是 TCL 语言本身,而是通过 C++ 扩展后的 TCL 语言。

在 TCL 语言中,一切皆命令,命令用字符串表示,变量也用字符串表示。这一点使其数据结构和语言更加简单。

OpenSees 的 Python 解释器

Python 是一种解释型语言。它与 C++ 的配合能力也很强,可以直接调用 C++ 函数。而且, Python 在近年来发展很快,有大量的库可以提供用户使用。 OpenSeesPy 也是一个 Python 库,可以使用 import 语句导入。使用 Python 作为 OpenSees 的解释器,最大的优点是可以与其它库配合,完成复杂的操作。

Tcl 和 Python 比较

Tcl 中,字符串是“一等公民”。而 Python 中,对象是“一等公民”。把握了这一点,就可以理解 TCL 和 Python 的区别。

TCL 一开始是不支持面向对象的,后来才加入了类、命名空间等而向对象的特点。但是最佳实践还是直接使用面向过程的编程。而 Python 一开始就是面向对象的,更适合面向对象的编程。

字符串

TCL 的字符串操作非常灵活。比如两个字符串的连接,在 TCL 中

1
2
3
4
set a "Hello"
set b "world"
set c "$a, $b!"
puts $c

可以直接在字符串中使用 $ 符号引用变量。而在 Python 中,则需要使用运算符

1
2
3
a, b = "Hello", "world"
c = a + ", " + b + "!"
print(c)

获使用 Python3 的字符串模板函数

1
2
3
a, b = "Hello", "world"
c = f"{a}, {b}!"
print(c)

列表

TCL 的列表操作,语法不复杂,但是写法比较复杂。列如

1
2
3
set a [list 1 2 3]
lappend a 4
puts [lindex $a 3]

操作上与 Python 很类似。

1
2
3
a = [1, 2, 3]
a.append(4)
print(a[3])

但是有一个很大的不同是, TCL 的列表是字符串,所以赋值时,默认是深拷贝。而 Python 的列表是对象,变量实际上代表的是一个引用,类似于 C++ 中的地址。

例如,在 TCL 中

1
2
3
4
5
set a [list 1 2 3]
set b $a
lappend a 4
puts $b
# Output: 1 2 3

在 Python 中

1
2
3
4
5
a = [1, 2, 3]
b = a
a.append(4)
print(b)
# Output: [1, 2, 3, 4]

字典

字典也是常用的数据结构。 TCL 使用字典可以通过两种方式实现。一种是 array ,一种是 dict

array 适用于比较简单的字典,不适合嵌套使用。它的变量使用比较方便,使用圆括号即可。例如

1
2
3
4
5
6
7
8
9
set a(1) 2
set a(x) "Hello"
array set a {"y" "world"}
puts [array names a]
# Output: x y 1
foreach name [array names a] {
puts $a($name)
}
# Output: Hello\nworld\n2

对于 dict 可以很多层嵌套。例如

1
2
3
4
5
6
7
dict set b 1 2
dict set b console x "Hello"
dict set b console y "world"
puts $b
# Output: 1 2 console {x Hello y world}
puts [dict get $b console]
# Output: x Hello y world

在 Python 中,内置了字典类型 dict 。例如

1
2
3
4
5
b = {}
b[1] = 2
b["console"] = {"x": "Hello", "y": "world"}
print(b)
# Output: {1: 2, 'console': {'x': 'Hello', 'y': 'world'}}

在赋值时,与列表相同, TCL 是深拷贝,而 python 是引用拷贝。这里不再赘述。

对于 TCL 和 Python 在作为 OpenSees 解释器中的异同,在 OpenSees 不同解释器的性能中进行介绍。