科学研究是每一个科研人必备的技能,那么科学研究应如何入手呢?通常科学研究应遵循如下标准流程:
XML解析入门
最近在研究编写飞行动力学模型,发现需要使用很多用于插值的数据,这些数据可以是一维向量、二维表格或三维数据。在代码中直接硬编码存储是不合适的,降低程序的灵活性。直接使用文本文档存储也不合适,这些插值数据明显具有结构化的特征。于是想到用XML来存储表示这些数据。下面总结介绍XML解析相关知识。
XML简介
可扩展标记语言(英语:Extensible Markup Language,简称:XML)是一种标记语言。标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。如何定义这些标记,既可以选择国际通用的标记语言,比如HTML,也可以使用像XML这样由相关人士自由决定的标记语言,这就是语言的可扩展性。XML是从标准通用标记语言(SGML)中简化修改出来的。它主要用到的有可扩展标记语言、可扩展样式语言(XSL)、XBRL和XPath等。
XML结构
XML定义结构、存储信息、传送信息。下例为小张发送给大元的便条,存储为XML。
1 | <?xml version="1.0"?> |
每个XML文档都由XML序言开始,在前面的代码中的第一行就是XML序言,。这一行代码会告诉解析器或浏览器这个文件应该按照XML规则进行解析。
但是,根元素到底叫<小纸条>还是<小便条>,则是由文档类型定义(DTD)或XML纲要(XML Schema)定义的。如果DTD规定根元素必须叫<小便条>,那么若写作<小纸条>就不符合要求。这种不符合DTD或XML纲要的要求的XML文档,被称作不合法的XML,反之则是合法的XML。
XML文件的第二行并不一定要包含文档元素;如果有注释或者其他内容,文档元素可以迟些出现。
XML解析器
C++类型XML解析器有:
- Boost.PropertyTree - A property tree parser/generator that can be used to parse XML/JSON/INI/Info files. [Boost]
- Expat - An XML parser library written in C. [MIT]
- Libxml2 - The XML C parser and toolkit of Gnome. [MIT]
- libxml++ - An XML Parser for C++. [LGPL2]
- Mini-XML - A small XML parsing library written in ANSI C. [LGPL2 with exceptions]
- PugiXML - A light-weight, simple and fast XML parser for C++ with XPath support. [MIT]
- RapidXml - An attempt to create the fastest XML parser possible, while retaining useability, portability and reasonable W3C compatibility. [Boost]
- TinyXML - A simple, small, minimal, C++ XML parser that can be easily integrating into other programs. [zlib]
- TinyXML2 - A simple, small, efficient, C++ XML parser that can be easily integrating into other programs. [zlib]
- TinyXML++ - A completely new interface to TinyXML that uses MANY of the C++ strengths. Templates, exceptions, and much better error * handling. [MIT]
- Xerces-C++ - A validating XML parser written in a portable subset of C++. [Apache2]
推荐使用TinyXML2。
参考链接
- C++解析xml有什么好用的轮子?,by 知乎.
- awesome-cpp xml,by fffaraz.
- XML与C++对象的相互转化,by Mr_John_Liang.
- JSON与XML的区别比较,by SanMaoSpace.
软件开发文档的编写方法
软件开发文档是软件开发过程的输出产物。软件开发过程的不同阶段将产生不同的软件开发文档。例如:软件需求分析阶段将产生软件需求规格说明书,软件概要设计阶段将产生概要设计说明书,软件详细设计阶段将产生详细设计说明书。按照软件工程的原则,软件开发过程输出这些文档的目的是为了保障软件开发的质量,确保软件项目能够按时完成,并保质保量。下面重点介绍各类软件开发文档的编写方法。
软件过程模型
软件过程模型是软件过程的简化表示。典型的软件过程模型有:瀑布模型、增量式开发模型和面向服用的软件工程模型。以瀑布模型为例,其涉及的开发活动如图1所示。
各个开发活动对应产出的软件开发文档主要有:
- 可行性研究报告
- 项目开发计划
- 软件需求说明书
- 概要设计说明书
- 详细设计说明书
- 数据库设计说明书
- 数据要求说明书
- 测试计划
- 测试分析报告
- 项目开发总结报告
- 操作手册
- 用户手册
- 开发进度月报
软件开发文档
可行性研究报告
项目开发计划
软件需求说明书
概要设计说明书
详细设计说明书
数据库设计说明书
数据要求说明书
测试计划
测试分析报告
项目开发总结报告
操作手册
用户手册
软件开发文档的使用
软件文档分类
软件文档读者
软件文档使用
参考链接
- 软件工程,by wikipedia.
- 软件需求,概要设计,详细设计(文档)怎么做,做什么?,by 安东尼_Anthony.
- 软件工程文档总结,by BONIC.
- 国标:计算机软件文档编制规范,by 宋哥.
- 软件测试流程,by HenryZ.Tang.
CPlusPlus不常用语法解析
近年来C++发展很快,出现了一些新的语法和特性。熟练掌握这些语法和特性,可提高编写C++代码的效率。下面即简要介绍这些C++语法和特性。
const=0
在类声明中,会出现const=0语法,如下所示:
1 | class Weapon |
在此处 =0 说明该类成员函数是一个纯虚函数。而将const放在成员函数之后,表示该成员函数禁止修改该类的数据成员(mutable成员除外)。如果您无意中修改了该类的数据成员,编译器会报告一个错误。
参考链接
- 关于virtual:c ++:const = 0的方法原型的代码说明,by 码农家园.
- C++构造函数和析构函数的调用顺序,by 靖心.
CPlusPlus单元测试框架Catch入门
最近在编写一个飞行力学的类库,随着类数量的增加,代码越来越复杂,质量越来越难以控制,因此引入单元测试,通过自动化测试以保障代码质量,防止因代码修改引入新的Bug。C++已经有一些成熟的代码测试框架,例如:Google Test, Boost.Test, CppUnit, Cute,等等。通过综合分析和比较,最终选择Catch2测试框架。选择该测试框架的原因是其够轻量级,够简单。
Catch2简介
Catch2是轻量级的C++的多范式测试框架。 它也支持Objective-C(也许还有C)。 它主要作为单个头文件分发,尽管某些扩展可能需要其他头文件。
关键特征
- 快速且非常容易上手。 只需下载catch.hpp,#include它就可以了。
- 没有外部依赖性。 只要您可以编译C ++ 11并拥有C ++标准库即可。
- 将测试用例编写为自注册函数(或方法,如果您愿意的话)。
- 将测试用例划分为几个部分,每个部分都是独立运行的(消除了对夹具的需求)。
- 使用BDD样式的“时准时限”部分以及传统的单元测试用例。
- 仅一个核心声明宏可以进行比较。 使用标准C / C ++运算符进行比较-但是完整的表达式已分解,并且记录了lhs和rhs值。
- 测试使用自由格式的字符串命名-合法标识符中没有其他名称。
核心特征
- 可以对测试进行标记,以方便地运行临时的测试组。
- 故障可能(可选)进入Windows和Mac上的调试器。
- 输出通过模块化报告器对象。 包括基本的文本和XML报告程序。 自定义记者可以轻松添加。
- 支持JUnit xml输出以与第三方工具(例如CI服务器)集成。
- 提供了默认的main()函数,但您可以提供自己的控件来进行完全控制(例如,集成到自己的测试运行器GUI中)。
- 提供了命令行解析器,如果您选择提供自己的main()函数,该解析器仍可以使用。
- Catch可以测试自己。
- 替代性断言宏报告失败,但不中止测试用例
- 浮点公差比较是使用表达性的Approx()语法构建的。
- 内部和友好的宏是隔离的,因此可以管理名称冲突
- 匹配器
Catch示例
使用Catch进行单元测试,只需简单掌握TEST_CASE、REQUIRE、SECTION三个宏即可编写绝大部分的测试用例。简单示例如下:
1 | TEST_CASE( "vectors can be sized and resized", "[vector]" ) { |
上述示例中,对于每个SECTION,TEST_CASE都是从头开始执行的,因此,当我们进入每个部分时,我们知道vector的大小为5,容量至少为5。通过REQUIRE宏在顶层确保vector大小和容量的正确性。这是可行的,因为SECTION宏包含一个if语句,该语句回调Catch来查看是否应执行该节。 通过TEST_CASE,每次运行都会执行一个叶子部分。 其他部分将被跳过。 下次执行下一个部分,依此类推,直到没有新的部分为止。
参考链接
- Writing Unit Tests with Catch and CMake,by filebox.
- Integrating catch2 with CMake and Jenkins,by mariuselvert.
- Catch2,by catchorg.
- C++单元测试入门,by pezy.
- Catch2 - 用于 test 的轻量级库,by Bluemultipl.
四元数与旋转矩阵
四元数是由爱尔兰数学家威廉·卢云·哈密顿在1843年创立出的数学概念。单位四元数(Unit quaternion)可以用于表示三维空间里的旋转。它与常用的另外两种表示方式(三维正交矩阵和欧拉角)是等价的,但是避免了欧拉角表示法中的万向锁问题。比起三维正交矩阵表示,四元数表示能够更方便地给出旋转的转轴与旋转角。
欧拉角
欧拉角(Euler Angles)是一种描述三维旋转的方式,根据欧拉旋转定理,任何一个旋转都可以用三个旋转的参数来表示。但欧拉角的描述方式有很多种,并没有一个统一标准。对于定义一个欧拉角,需要明确的内容包括:
- 三个旋转角的组合方式(是xyz还是yzx还是zxy)
- 旋转角度的参考坐标系统(旋转是相对于固定的坐标系还是相对于自身的坐标系)
- 使用旋转角度是左手系还是右手系
- 三个旋转角的记法
旋转角的记法
| 顺序 | 飞行器 | 望远镜 | 符号 | 角速度 |
|---|---|---|---|---|
| 第一 | heading | azimuth | $θ$ | yaw |
| 第二 | attitude | elevation | $ϕ$ | pitch |
| 第三 | bank | tilt | $ψ$ | roll |
旋转矩阵
对于两个三维点 $p_1(x_1, y_1, z_1)$,$p_2(x_2,y_2,z_2)$,由点 $p_1$ 经过旋转矩阵 $R$ 旋转到 $p_2$,则有:
$$R = \left[ \begin{matrix}
r_{11} & r_{12} & r_{13}\
r_{21} & r_{22} & r_{23}\
r_{31} & r_{32} & r_{33}
\end{matrix} \right]$$
$$\left[ \begin{matrix}
x_2 \
y_2 \
z_2
\end{matrix} \right] = R
\left[ \begin{matrix}
x_1 \
y_1 \
z_1
\end{matrix} \right]$$
注:旋转矩阵为正交矩阵$RR^T=E$
- 绕x轴旋转:
$$R_x(\theta) = \left[ \begin{matrix}
1 & 0 & 0\
0 & cos\theta & -sin\theta\
0 & sin\theta & cos\theta
\end{matrix} \right]$$
- 绕y轴旋转:
$$R_y(\theta) = \left[ \begin{matrix}
cos\theta & 0 & sin\theta\
0 & 1 & 0\
-sin\theta & 0 & cos\theta
\end{matrix} \right]$$
- 绕z轴旋转:
$$R_z(\theta) = \left[ \begin{matrix}
cos\theta & -sin\theta & 0\
sin\theta & cos\theta & 0\
0 & 0 & 1
\end{matrix} \right]$$
- 任意旋转矩阵:
任何一个旋转可以表示为依次绕着三个旋转轴旋三个角度的组合。这三个角度称为欧拉角。
由欧拉角求旋转矩阵
设三个轴$x,y,z$的欧拉角分别为$θ_x,θ_y,θ_z$,正弦值、余弦值分别为$s_x, c_x, s_y, c_y, s_z, c_z$那么旋转矩阵为:
$$R(\theta_x,\theta_y,\theta_z)=R_z(\theta_z)R_y(\theta_y)R_x(\theta_x) = \left[ \begin{matrix}
c_y c_z & c_z s_x s_y - c_x s_z & s_x s_z + c_x c_z s_y\
c_y s_z & c_x c_z + s_x s_y s_z & c_x s_y s_z - c_z s_z\
-s_y & c_y s_x & c_x c_y
\end{matrix} \right]$$
由旋转矩阵求欧拉角
$$R = \left[ \begin{matrix}
r_{11} & r_{12} & r_{13}\
r_{21} & r_{22} & r_{23}\
r_{31} & r_{32} & r_{33}
\end{matrix} \right] = \left[ \begin{matrix}
c_y c_z & c_z s_x s_y - c_x s_z & s_x s_z + c_x c_z s_y\
c_y s_z & c_x c_z + s_x s_y s_z & c_x s_y s_z - c_z s_z\
-s_y & c_y s_x & c_x c_y
\end{matrix} \right]$$
解方程可得:
$$\theta_x = atan2(r_{32}, r_{33})$$
$$\theta_y = atan2(-r_{31}, \sqrt{r_{32}^2+r_{33}^2})$$
$$\theta_z = atan2(r_{21}, r_{11})$$
注意:atan2()为C++中的函数,atan2(y,x) 求的是y/x的反正切,其返回值为[-pi,+pi]之间的一个数。
四元数
三维空间的任意旋转,都可以用绕三维空间的某个轴旋转过某个角度来表示,即所谓的Axis-Angle表示方法。这种表示方法里,Axis可用一个三维向量$(x,y,z)$来表示,$θ$可以用一个角度值来表示,直观来讲,一个四维向量$(θ,x,y,z)$就可以表示出三维空间任意的旋转。注意,这里的三维向量$(x,y,z)$只是用来表示axis的方向朝向,因此更紧凑的表示方式是用一个单位向量来表示方向axis,而用该三维向量的长度来表示角度值$θ$。这样以来,可以用一个三维向量$(θ∗x,θ∗y,θ∗z)$就可以表示出三维空间任意的旋转,前提是其中$(x,y,z)$是单位向量。这就是旋转向量(Rotation Vector)的表示方式,OpenCV里大量使用的就是这种表示方法来表示旋转(见OpenCV相机标定部分的rvec)。
- 单位向量(x,y,z)旋转θ角度后的四元数:
$$(cos \frac{\theta}{2}, xsin \frac{\theta}{2}, ysin \frac{\theta}{2}, z*sin \frac{\theta}{2})$$
- 对于三维坐标的旋转,可以通过四元数乘法直接操作,与旋转矩阵操作可以等价,但是表示方式更加紧凑,计算量也可以小一些。
四元数求旋转矩阵
已知四元数:
$$\mathbf{q} = q_0 + q_1 i + q_2 j + q_3 k = [s, \mathbf{v}]$$
利用Rodrigues公式可以由四元数求得旋转矩阵R:
$$R = \left[ \begin{matrix}
1 - 2 q_2^2 - 2 q_3^2 & 2q_1 q_2 - 2q_0 q_3 & 2 q_1 q_3 + 2 q_0 q_2 \
2q_1 q_2 + 2q_0 q_3 & 1 - 2 q_1^2 - 2 q_3^2 & 2 q_2 q_3 - 2 q_0 q_1 \
2 q_1 q_3 - 2 q_0 q_2 & 2 q_2 q_3 + 2 q_0 q_1 & 1 - 2 q_1^2 - 2 q_2^2
\end{matrix} \right ]$$
旋转矩阵求四元数
给出其中一种情况的计算方法:
$$q_0 = \frac{\sqrt{1+r_{11}+r_{22}+r_{33}}}{2}$$
$$q_1 = \frac{r_{32}-r_{23}}{4q_0}$$
$$q_2 = \frac{r_{13}-r_{31}}{4q_0}$$
$$q_3 = \frac{r_{21}-r_{12}}{4q_0}$$
其中要满足 $q_0 \neq 0$,$1+r_{11}+r_{22}+r_{33}>0$,即 $1+tr(R)>0$
四元数进行姿态变换
假设坐标系O1上的点P1(x1, y1, z1), 存在变换矩阵R, 可计算P1点在坐标系O2上的坐标值为P2(x2, y2, z2):
$$P2=R∗P1$$
矩阵R对应的四元数为q, 则使用四元数计算为:
首先三维空间点用一个虚四元数来描述:P1=[0, x1, y1, z1], P2=[0, x2, y2, z2]
则P2和P1将计算关系为:
$$P_2=qP_1q^{-1}$$
参考链接
- 四元数,by wikipedia.
- 四元数与空间旋转,by wikipedia.
- 从旋转矩阵计算欧拉角,by PengChao.
- 旋转变换(一)旋转矩阵,by csxiaoshui.
- 旋转矩阵、欧拉角、四元数理论及其转换关系,by jason_ql.
- 旋转变换(二)欧拉角,by csxiaoshui.
- 欧拉角,by wikipedia.
- 欧拉角细节/旋转顺序/内旋外旋,by 能儿.
- 四元数运算与姿态变换,by Yoyo_wym.
- Flight Parameters and Quaternion Math,by mathwork.
JSBSim源代码分析
JSBSim是一个开源跨平台的飞行动力学模型(FDM)软件库,用于模拟航空航天飞行器的飞行动力学。 该库已被纳入飞行模拟软件包FlightGear和OpenEaagles。JSBSim可以独立运行,通过命令行参数指定飞行器和初始状态,进行简单情境下的飞行动力学仿真,也可以将JSBSim作为代码库,编程实现飞行器模型加载,设置输入,获得输出。下面将通过分析JSBSim源代码,研究其实现通用飞行动力学模型的方法。
入口分析
下面是JSBSim参考手册中的最简单实例,因JSBSim的不断开发,JSBSim参考手册中该编程实例有点过时,因此进行了少量修改。
1 | #include <FGFDMExec.h> |
从上述代码可知,调用JSBSim的主要方法是利用FGFDMExec类,通过实例化一个FGFDMExec类,就相当于获得了一个运行JSBSim仿真的工具箱,通过这个工具箱就可以调用JSBSim的大部分功能,实现我们要的仿真目标。同时FGFDMExec类通过加载外部飞机的XML脚本,实现飞行动力学模型的通用性。
JSBSim初始化流程
上述JSBSim最简仿真示例中已包含其初始化流程,采用图示如下:
FGFDMExec初始化
FGFDMExec类在其构造函数中对各个模型进行初始化,具体代码在Allocate函数中:
1 | FGFDMExec::FGFDMExec(FGPropertyManager* root, unsigned int* fdmctr) |
Allocate函数代码如下:
1 | bool FGFDMExec::Allocate(void) |
Allocate函数代码中需要注意LoadInputs函数,该函数决定各个子模型的初始化顺序,确定各个子模型的输入输出,具体代码如下:
1 | void FGFDMExec::LoadInputs(unsigned int idx) |
FGFDMExec加载飞机配置
FGFDMExec的LoadScript函数在初始化时负责加载飞机配置,用于初始化各个子模型。
1 | bool FGFDMExec::LoadScript(const SGPath& script, double deltaT, |
FGFDMExec运行
FGFDMExec的Run函数负责飞行动力学模型的计算,其代码如下:
1 | bool FGFDMExec::Run(void) |
FGFDMExec的Run函数将依次调用各个子模型的Run函数。
参考链接
- JSBSim编程实践之入门,by jackhuang.
排序算法总结
排序算法是计算机科学的基石之一,可从时间复杂度、空间复杂度、稳定性、是否原地排序等维度对排序算法进行分类。下面从时间复杂度方面对排序算法进行分类。
O(n^2)算法
冒泡排序
选择排序
插入排序
O(nlogn)算法
希尔排序
快速排序
归并排序
堆排序
O(n)算法
计数排序
桶排序
基数排序
参考链接
- 漫画:“排序算法” 大总结,by 小灰.
- 分布式哈希表 (DHT) 和 P2P 技术,by luyuhuang.
- Gzip 格式和 DEFLATE 压缩算法,by Luyu Huang.
Pandas入门教程
Pandas是一个开源的,BSD许可的库,为Python编程语言提供高性能,易于使用的数据结构和数据分析工具。
Pandas特色
Pandas 适用于处理以下类型的数据:
- 与 SQL 或 Excel 表类似的,含异构列的表格数据;
- 有序和无序(非固定频率)的时间序列数据;
- 带行列标签的矩阵数据,包括同构或异构型数据;
- 任意其它形式的观测、统计数据集, 数据转入 Pandas 数据结构时不必事先标记。
Pandas数据结构
Pandas 的主要数据结构是 Series(一维数据)与 DataFrame(二维数据),这两种数据结构足以处理金融、统计、社会科学、工程等领域里的大多数典型用例。对于 R 用户,DataFrame 提供了比 R 语言 data.frame 更丰富的功能。Pandas 基于 NumPy 开发,可以与其它第三方科学计算支持库完美集成。
| 维数 | 名称 | 描述 |
|---|---|---|
| 1 | Series | 带标签的一维同构数组 |
| 2 | DataFrame | 带标签的,大小可变的,二维异构表格 |
Pandas用法
Pandas用法与Matlab中矩阵操作很类似,熟悉Matlab操作的同学可以很快上手Pandas。
生成对象
生成Series对象:
1 | In [3]: s = pd.Series([1, 3, 5, np.nan, 6, 8]) |
生成DataFrame对象:
1 | In [7]: df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD')) |
列切片
1 | df1 = df[df.columns[0:6]]; |
重命名列名
1 | df1.columns['id','name','sex','age','department','work'] |
过滤行
1 | df1 = df1[df1.iloc[:,1]=='YES'] |
合并表格
pandas 2.0实现数据的合并与拼接,主要有三种方法:
- join 最简单,主要用于基于索引的横向合并拼接
- merge 最常用,主要用于基于指定列的横向合并拼接
- concat最强大,可用于横向和纵向合并拼接
1 | # 合并两个表,df1 和 df2 表结构一样 |
毫秒解析
1 | timeDelta = datetime.datetime(2024,11,11) - datetime.datetime(1900,1,1) |
按时间排序
1 | df3 = df3.sort_values(by="datetime") |
输出CSV表格
1 | df3.to_csv(filePath + 'test.csv',index=0) |
参考链接
- Pandas 中文,by pypandas.
- 十分钟入门 Pandas,by pypandas.
- Python读取csv文件的三种方式,by 涛声依旧2019.
- Python模块化开发组织代码程序示例,by BabyFish13.
- Python最佳实践指南!,by Prodesire.
- pandas中DataFrame 数据合并,连接(merge,join,concat) ,by Vincent-yuan.
- 【已解决】AttributeError: ‘DataFrame‘ object has no attribute ‘append‘,by zhtstar.
- pandas DataFrame数据重命名列名的几种方式,by littleRpl.
- Pandas 毫秒级时间解析,by 一定波兮.
jupyter notebook安装与使用
Jupyter Notebook(前身是IPython Notebook)是一个基于Web的交互式计算环境,用于创建Jupyter Notebook文档。Notebook一词可以通俗地引用许多不同的实体,主要是Jupyter Web应用程序、Jupyter Python Web服务器或Jupyter文档格式(取决于上下文)。Jupyter Notebook文档是一个JSON文档,遵循版本化模式,包含一个有序的输入/输出单元格列表,这些单元格可以包含代码、文本(使用Markdown语言)、数学、图表和富媒体,通常以“.ipynb”结尾扩展。
安装过程
安装前提
- python>3.3 或者python=2.7
安装步骤
1 | pip install notebook |
启动Jupyter Notebook
1 | jupyter notebook |
使用指南
- 单元格执行状态
单元格的执行状态对于复杂度高的代码,往往会意味着更长的执行等待时间。在Jupyter Notebook 中,当一个单元格处于执行状态时,单元格前面会出现 In [*] 符号,只有执行完成的单元格, [] 中的 * 才会变成相应的 序号。
除此之外,你可以通过页面右上角的 Kernel 状态指示器判断内核占用情况。如果 Python 字符右边出现了实心圆圈 ◉,代表内核处于占有状态。而空心圆圈 ◯ 则代表内核处于空闲状态。当然,也可能出现链接断开的符号,那就代表着内核已经断开链接,你可能需要刷新页面或重启实验环境。
参考链接
- Jupyter,by wikipedia.
- Installing the Jupyter Software,by jupyter.
- Matplotlib animation not working in IPython Notebook (blank plot),by stackoverflow.
- Jupyter Notebook使用指南,by zhanlang619.
- Jupyter中markdown的操作技巧,by 那一年_我九岁.