关于 fortran
面向科学计算和数值计算的现代 Fortran 开发技能,指导开发者组织模块、定义精度参数、设计过程、管理数组,并进行构建与数值测试。
主要功能
- 01现代模块组织方式
- 02共享精度参数与派生类型
- 03显式过程接口
- 04可分配数组安全管理
- 05启用警告的构建与测试
- 06基于容差的数值验证
适用场景
- 01设计科学计算 Fortran 模块
- 02统一定义数值精度
- 03安全管理动态数组
- 04跨构建环境测试数值结果
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill fortran安装后在 Claude Code 中输入/fortran确认已加载。
详细介绍
现代 Fortran 开发
本技能涵盖如何编写现代、易维护的 Fortran(2003/2008 及更高版本),用于科学计算和数值计算,包括模块组织、kind 参数、过程设计、内存安全和测试。
编写现代 Fortran 模块的工作流
- 先定义共享 kind — 创建一个
kinds_mod(或类似名称)模块,使用iso_fortran_env或selected_real_kind/selected_int_kind参数,供整个项目使用。 - 设计模块 — 将相关的派生类型和过程归入每个文件中的一个专注模块;在顶部声明
implicit none。 - 编写过程接口 — 为每个哑元参数指定明确的
intent(in)、intent(out)或intent(inout);使用use, only:仅导入所需内容。 - 尽早验证后再实现 — 在每个过程开头检查前置条件(数组边界、分配状态、有效范围),尽早返回或停止,而不是进行深层嵌套。
- 显式管理数组 — 使用可分配数组,使用前检查
allocated(),并在其生命周期不受作用域自然管理时释放数组。 - 启用警告进行构建 — 使用
-Wall -Wextra -std=f2008(gfortran)或等效选项编译,并在 CI 中将新增警告视为失败。 - 测试 — 为各个过程编写单元测试,为完整数值工作流编写集成测试;检查容差,而不是要求浮点数精确相等。
基本原则
- 面向现代 Fortran 标准 — Fortran 2003、2008 或更新版本 — 不要仅仅因为编译器仍然接受旧式 FORTRAN 77 写法,就沿用这种风格。
- 在每个程序单元(模块、程序,以及通过模块继承或显式声明的每个过程)中加入
implicit none,以便在编译时捕获未声明变量的拼写错误,避免产生悄无声息的错误结果。 - 将过程放在模块中,而不是写成外部子程序;模块过程会自动生成显式接口,让编译器能够捕获外部过程无法检查的参数不匹配。
- 让模块专注于单一职责,并将每个主要模块放在独立文件中,文件名与模块名一致(例如,在
linear_solver.f90中定义module linear_solver)。 - 优先采用清晰、结构化的代码,而非巧妙的语言技巧 — Fortran 数值代码被阅读的次数远多于编写的次数,而且通常由原作者以外的人阅读。
- 避免使用过时特性:
COMMON块(用模块替代)、大量使用GOTO的控制流(用结构化的if/do/select case替代,并在适当时使用block),以及用作跳转目标的数字语句标签。
Kind 和类型
- 在一个共享模块中定义数值 kind 参数(例如
kind_mod或precision_mod),使整个代码库能够在一个地方更改精度。 - 浮点数使用
real(kind=dp)(或项目批准的实数 kind)— 不要使用不带 kind 的real或double precision,因为它们的实际精度取决于编译器和编译选项。 - 对于位宽很重要的整数,使用
integer(kind=i4)(或项目批准的整数 kind),尤其是在与 C 接口或进行二进制 I/O 时。 - 显式且精确地定义
pi等常量(例如real(dp), parameter :: pi = 4.0_dp * atan(1.0_dp)),不要使用被截断的字面量。 - 对于表示物理量的变量,在注释中注明物理单位(
real(dp) :: velocity ! m/s)。 - 使用派生类型对相关数据分组(例如,定义包含位置、速度和质量字段的
particle_t类型),而不是在过程调用中传递许多零散的基本类型参数。
示例:Kinds 模块和数值过程
module kinds_mod
use iso_fortran_env, only: real64, int32
implicit none
private
public :: dp, i4
integer, parameter :: dp = real64
integer, parameter :: i4 = int32
end module kinds_mod
module stats_mod
use kinds_mod, only: dp
implicit none
private
public :: mean, standard_deviation
contains
pure function mean(x) result(m)
real(dp), intent(in) :: x(:)
real(dp) :: m
m = sum(x) / real(size(x), dp)
end function mean
pure function standard_deviation(x) result(s)
real(dp), intent(in) :: x(:)
real(dp) :: s
real(dp) :: m
integer :: n
n = size(x)
if (n < 2) then
s = 0.0_dp
return
end if
m = mean(x)
s = sqrt(sum((x - m)**2) / real(n - 1, dp))
end function standard_deviation
end module stats_mod
program demo
use kinds_mod, only: dp
use stats_mod, only: mean, standard_deviation
implicit none
real(dp) :: samples(5)
samples = [1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp, 5.0_dp]
print '(A, F0.4)', 'mean = ', mean(samples)
print '(A, F0.4)', 'stddev = ', standard_deviation(samples)
end program demo
命名和风格
- 语言关键字和大多数标识符使用小写;Fortran 不区分大小写,因此统一使用小写可以避免大小写混用造成的视觉干扰。
- 多词名称使用下划线(
particle_velocity,而不是particleVelocity或ParticleVelocity)。 - 避免使用仅大小写不同的名称(
Count和count),因为 Fortran 会将它们视为相同名称,容易造成混淆。 - 为过程和状态使用描述性名称(
compute_residual,而不是cr或calc2)。 - 在
end语句后重复过程或模块名称(end module kinds_mod、end subroutine compute_residual),便于快速核对较长的代码单元。 - 在
do、if、select case和模块代码块中保持缩进一致(使用 2 或 4 个空格,与项目保持一致)。
过程
- 子程序和函数应简短且职责单一——计算残差的过程不应同时写入输出文件。
- 为每个哑元参数指定
intent(in)、intent(out)或intent(inout);未指定intent的参数是一个警示信号,表明接口尚未经过仔细设计。 - 尽可能让函数没有副作用(符合条件时标记为
pure或elemental),并将subroutine留给会修改状态或执行 I/O 的过程。 - 优先进行提前验证并清晰返回(
if (n <= 0) then; ...; return; end if),避免使用层层嵌套的if代码块。 - 从模块导入内容时,使用
use, only: name1, name2,而不是直接使用use module_name,这样能清楚看出调用处具体导入了什么,也能避免名称冲突。
内存和数组
- 优先使用可分配数组而非指针,除非确实需要指针语义(别名、链式结构);可分配数组会自动释放,默认情况下也更安全。
- 使用可能尚未分配的数组前,检查其
allocated()状态和数组大小(size()、lbound()、ubound())。 - 当可分配数组的生命周期不会自然结束时,显式释放它们(例如,数组保存在生命周期超过单次过程调用的派生类型中);过程局部数组会在退出时自动释放。
- 在数组边界很重要时明确指定,尤其是非默认下界(
real(dp) :: a(0:n))。 - 避免在热点循环中进行不必要的动态分配——在循环外分配一次并重复使用缓冲区,或对小型、固定大小的临时数组使用自动数组或栈数组。
测试和构建
- 使用项目已统一采用的构建系统,例如 CMake、fpm(Fortran Package Manager)、Make;保持一致,不要混用。
- 启用编译警告(开发构建可使用
gfortran -Wall -Wextra -std=f2008 -fcheck=all),并将重要警告视为 CI 失败。 - 为公共过程添加单元测试(例如,纯函数
standard_deviation应有一个输入和输出已知、快速运行的小型测试),并为完整的数值工作流添加集成测试。 - 测试边界条件(空数组、单元素数组、零值或负值输入)、无效输入,以及源自实际问题领域的典型科学案例。
- 使用容差验证数值结果(例如,
abs(actual - expected) < 1.0e-10_dp),不要依赖浮点数精确相等,因为跨编译器或优化级别几乎无法保证结果完全一致。
常见错误
- 在可执行语句之后声明变量,却没有将其放在
block结构中 — Fortran 要求在给定作用域单元中,所有声明都必须位于可执行代码之前。 - 误以为
random_number是函数;它是子程序(应使用call random_number(x),而不是x = random_number())。 - 在声明为
pure的过程中向标准输出写入内容(print、write(*,*))— 这是不允许的,会导致编译失败。 - 在同一作用域中重复声明同一个变量;模块变得很大时很容易忽略这一点。
- 误以为
pi、dp或其他项目标准的 kind/常量名称已经存在,却没有通过use显式导入或定义它们。
评论
支持 Markdown