mcpField
F
fortran

fortran

github.com/mindrally开发28765 次安装3 周前更新
代码仓库

关于 fortran

面向科学计算和数值计算的现代 Fortran 开发技能,指导开发者组织模块、定义精度参数、设计过程、管理数组,并进行构建与数值测试。

主要功能

  1. 01现代模块组织方式
  2. 02共享精度参数与派生类型
  3. 03显式过程接口
  4. 04可分配数组安全管理
  5. 05启用警告的构建与测试
  6. 06基于容差的数值验证

适用场景

  1. 01设计科学计算 Fortran 模块
  2. 02统一定义数值精度
  3. 03安全管理动态数组
  4. 04跨构建环境测试数值结果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mindrally/skills --skill fortran

安装后在 Claude Code 中输入/fortran确认已加载。

详细介绍

现代 Fortran 开发

本技能涵盖如何编写现代、易维护的 Fortran(2003/2008 及更高版本),用于科学计算和数值计算,包括模块组织、kind 参数、过程设计、内存安全和测试。

编写现代 Fortran 模块的工作流

  1. 先定义共享 kind — 创建一个 kinds_mod(或类似名称)模块,使用 iso_fortran_env 或 selected_real_kind/selected_int_kind 参数,供整个项目使用。
  2. 设计模块 — 将相关的派生类型和过程归入每个文件中的一个专注模块;在顶部声明 implicit none。
  3. 编写过程接口 — 为每个哑元参数指定明确的 intent(in)、intent(out) 或 intent(inout);使用 use, only: 仅导入所需内容。
  4. 尽早验证后再实现 — 在每个过程开头检查前置条件(数组边界、分配状态、有效范围),尽早返回或停止,而不是进行深层嵌套。
  5. 显式管理数组 — 使用可分配数组,使用前检查 allocated(),并在其生命周期不受作用域自然管理时释放数组。
  6. 启用警告进行构建 — 使用 -Wall -Wextra -std=f2008(gfortran)或等效选项编译,并在 CI 中将新增警告视为失败。
  7. 测试 — 为各个过程编写单元测试,为完整数值工作流编写集成测试;检查容差,而不是要求浮点数精确相等。

基本原则

  • 面向现代 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