理解 implicit interfaces和 compile-time polymorphism

本文探讨了面向对象编程与泛型编程的区别,重点讲解了显式接口与隐式接口的概念,以及运行期多态与编译期多态的不同。
 
Object-Oriented Programming(面向对象编程)的世界是围绕着 explicit interfaces(显式接口)和 runtime polymorphism(执行期多态)为中心的。例如,给出下面这个(没有什么意义的)class(类)
class Widget {
public:
  Widget();
  virtual ~Widget();
  virtual std::size_t size() const;
  virtual void normalize();
  void swap(Widget& other);
  ...
};

以及这个(同样没有什么意义的)function(函数)
void doProcessing(Widget& w)
{
  if (w.size() > 10 && w != someNastyWidget) {
      Widget temp(w);
      temp.normalize();
      temp.swap(w);
  }
}

我们可以这样谈论 doProcessing 中的 w
因为 w 被声明成类型 Widget,则 w 必须支持 Widget interface(接口)。我们可以在源代码中找到这个 interface(接口)(例如,Widget.h 文件)以看清楚它是什么样子的,所以我们称其为一个 explicit interface(显式接口)——它在源代码中显式可见。

因为 Widget 的一些 member functions(成员函数)是虚拟的,w 对这些函数的调用就表现为 runtime polymorphism(执行期多态):被调用的特定函数在执行期基于 w 的 dynamic type(动态类型)来确定。

templates(模板)和 generic programming(泛型编程)的世界是根本不同的。在那个世界,explicit interfaces(显式接口)和 runtime polymorphism(执行期多态)继续存在,但是它们不那么重要了。反而,implicit interfaces(隐式接口)和 compile-time polymorphism(编译期多态)移到了前台。为了了解这是怎样一种情况,看一下当我们把 doProcessing 从一个 function(函数)变成一个 function template(函数模板)时会发生什么:
template<typename T>
void doProcessing(T& w)
{
  if (w.size() > 10 && w != someNastyWidget) {
     T temp(w);
     temp.normalize();
     temp.swap(w);
  }
}

现在我们可以如何谈论 doProcessing 中的 w 呢?
w 必须支持的 interface(接口)是通过 template(模板)中在 w 身上所执行的操作确定的。在本例中,它显现为 w 的类型 (T) 必须支持 sizenormalizeswap member functions(成员函数);copy construction(拷贝构造函数)(用于创建 temp);以及对不等于的比较(用于和 someNastyWidget 之间的比较)。我们将在以后看到这并不很精确,但是对于现在来说它已经足够正确了。重要的是这一系列为了模板能够编译必须合法的表达式就是 T 必须支持的 implicit interface(隐式接口)。

对诸如 operator>operator!= 这样的涉及 w 的函数的调用可能伴随 instantiating templates(实例化模板)以使这些调用成功。这样的 instantiation(实例化)发生在编译期间。因为用不同的 template parameters(模板参数)实例化 function templates(函数模板)导致不同的函数被调用,因此以 compile-time polymorphism(编译期多态)闻名。

即使从没有使用过模板,也应该熟悉 runtime(运行期)和 compile-time polymorphism(编译期多态)之间的区别,因为它类似于确定一系列重载函数中哪一个应该被调用的过程(这个发生在编译期)和 virtual function(虚拟函数)调用的 dynamic binding(动态绑定)(这个发生在运行期)之间的区别。explicit(显式)和 implicit interfaces(隐式接口)之间的区别是与 templates(模板)有关的新内容,需要对他进行近距离的考察。

一个 explicit interface(显式接口)一般由 function signatures(函数识别特征)组成,也就是说,函数名,参数类型,返回类型,等等。例如,Widget class(类)的 public interface(公有接口)
class Widget {
public:
 Widget();
  virtual ~Widget();
  virtual std::size_t size() const;
  virtual void normalize();
 void swap(Widget& other);
};
由一个 constructor(构造函数),一个 destructor(析构函数),以及函数 sizenormalizeswap 组成,再加上 parameter types(参数类型),return types(返回类型)和这些函数的 constnesses(常量性)。(它也包括 compiler-generated(编译器生成)的 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符))它还可能包含 typedefs,还有,如果让 data members(数据成员)private(私有)的建议,那就包括 data members(数据成员),虽然在当前情况下,它不是。

一个 implicit interface(隐式接口)有很大不同。它不是基于 function signatures(函数识别特征)的。它是由 valid expressions(合法表达式)组成的。再看一下在 doProcessing template 开始处的条件:
template<typename T>
void doProcessing(T& w)
{
  if (w.size() > 10 && w != someNastyWidget) {
  ...
对于 Tw 的类型)的 implicit interface(隐式接口)看起来有如下这些约束:
它必须提供一个名为 size 的返回一个数值的 member function(成员函数)。
它必须支持一个用于比较两个类型 T 的对象的 operator!= 函数。(这里,我们假定 someNastyWidget 的类型为 T。)
由于 operator overloading(运算符重载)的可能性,这两个约束都不必满足。是的,T 必须支持一个 size member function(成员函数),可是值得一提的是这个函数可以是从一个 base class(基类)继承来的。但是这个 member function(成员函数)不必返回一个整数类型。它甚至不必返回一个数值类型。对于这种情况,它甚至不必返回一个定义了 operator> 的类型!它要做的全部就是返回某种类型 X 的一个 object(对象),存在一个可以由一个类型 X 的 object(对象)和一个 int(因为 10 为 int 类型)来调用的 operator>。这个 operator> 不一定非要取得一个类型 X 的参数,因为只要存在一个从类型 X 的 objects(对象)到类型 Y 的 objects(对象)的 implicit conversion(隐式转型),它只取得一个类型 Y 的参数也是可以的!
类似地,T 支持 operator!= 也是没有必要的,因为正像 operator!= 取得一个类型 X 的 objects(对象)和一个类型 Y 的 objects(对象)是可接受的一样。只要 T 能转型为 X,而 someNastyWidget 的类型能够转型为 Y,对 operator!= 的调用就是合法的。
(旁注:此处的分析没有考虑 operator&& 被重载的可能性,这会将上面的表达式的含义从逻辑与转换到某些大概完全不同的东西。)

第一次考虑 implicit interfaces(隐式接口)的时候,大多数人都会头疼。implicit interfaces(隐式接口)简单地由一套 valid expressions(合法表达式)构成。这些表达式自身看起来可能很复杂,但是它们施加的约束通常是简单易懂的。例如,给出这个条件,
if (w.size() > 10 && w != someNastyWidget) ...
关于 functions sizeoperator>operator&&operator!= 上的约束很难说出更多的东西,但是要识别出整个表达式的约束是非常简单的。一个 if 语句的条件部分必须是一个 boolean expression(布尔表达式),所以不管 "w.size() > 10 && w != someNastyWidget" 所产生的精确类型是哪一种,它必须与 bool 相容。这就是 template(模板)doProcessing 施加于它的 type parameter(类型参数)T 之上的 implicit interface(隐式接口)的一部分。doProcessing 必需的 interface(接口)的其余部分是 copy constructor(拷贝构造函数),normalizeswap 的调用对于类型 T 的 objects(对象)来说必须是合法的。

implicit interface(隐式接口)对 template(模板)的 parameters(参数)施加的影响正像 explicit interfaces(显式接口)对一个 class(类)的 objects(对象)施加的影响,而且这两者都在编译期间被检查。正像你不能用与它的 class(类)提供的 explicit interface(显式接口)矛盾的方法使用 object(对象)(代码无法编译)一样,除非一个 object(对象)支持 template(模板)所必需的 implicit interface(隐式接口),否则你就不能在一个 template(模板)中试图使用这个 object(对象)(代码还是无法编译)。

Things to Remember
classes(类)和 templates(模板)都支持 interfaces(接口)和 polymorphism(多态)。

对于 classes(类),interfaces(接口)是 explicit(显式)的并以 function signatures(函数识别特征)为中心。polymorphism(多态性)通过 virtual functions(虚拟函数)出现在运行期。
对于 template parameters(模板参数),interfaces(接口)是 implicit(隐式)的并基于 valid expressions(合法表达式)。polymorphism(多态性)通过 template instantiation(模板实例化)和 function overloading resolution(函数重载解析)出现在编译期。

 
. C:\practice\practice.f90 C:\practice\practice.f90(43) : Error: Error in opening the Library module file. [ISO_FORTRAN_ENV] use iso_fortran_env, only: dp => real64 --------^ C:\practice\practice.f90(48) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), parameter :: V0 = 1090.0_dp ! eV ---------^ C:\practice\practice.f90(48) : Error: The use-name for this local-name is not defined. [DP] real(dp), parameter :: V0 = 1090.0_dp ! eV --------------------------------------^ C:\practice\practice.f90(48) : Error: A kind-param must be a digit-string or a scalar-int-constant-name [DP] real(dp), parameter :: V0 = 1090.0_dp ! eV --------------------------------------^ C:\practice\practice.f90(49) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), parameter :: r0 = 0.321_dp ! ? ---------^ C:\practice\practice.f90(49) : Error: The use-name for this local-name is not defined. [DP] real(dp), parameter :: r0 = 0.321_dp ! ? -------------------------------------^ C:\practice\practice.f90(49) : Error: A kind-param must be a digit-string or a scalar-int-constant-name [DP] real(dp), parameter :: r0 = 0.321_dp ! ? -------------------------------------^ C:\practice\practice.f90(50) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), parameter :: b = 1.0_dp ! eV ---------^ C:\practice\practice.f90(50) : Error: The use-name for this local-name is not defined. [DP] real(dp), parameter :: b = 1.0_dp ! eV ----------------------------------^ C:\practice\practice.f90(50) : Error: A kind-param must be a digit-string or a scalar-int-constant-name [DP] real(dp), parameter :: b = 1.0_dp ! eV ----------------------------------^ C:\practice\practice.f90(51) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), parameter :: c = 0.1_dp ! ? ---------^ C:\practice\practice.f90(51) : Error: The use-name for this local-name is not defined. [DP] real(dp), parameter :: c = 0.1_dp ! ? ----------------------------------^ C:\practice\practice.f90(51) : Error: A kind-param must be a digit-string or a scalar-int-constant-name [DP] real(dp), parameter :: c = 0.1_dp ! ? ----------------------------------^ C:\practice\practice.f90(53) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp) :: x(3,n), x_reduced(ndim), grad(ndim) ---------^ C:\practice\practice.f90(54) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp) :: energy, tol ---------^ C:\practice\practice.f90(98) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(in) :: x_red(ndim) -------------^ C:\practice\practice.f90(99) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(out) :: x_full(3,n) -------------^ C:\practice\practice.f90(138) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(in) :: x_red(ndim) -------------^ C:\practice\practice.f90(139) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(out) :: e, g(ndim) -------------^ C:\practice\practice.f90(140) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp) :: x_full(3,n), dx(3), r_ij, f_rep, f_att, dVdr -------------^ C:\practice\practice.f90(142) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp) :: eta, delta, rep_term, att_term -------------^ C:\practice\practice.f90(200) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(in) :: force_on_i(3), x_red(ndim) -------------^ C:\practice\practice.f90(201) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(inout) :: grad(ndim) -------------^ C:\practice\practice.f90(202) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp) :: x_full(3,n) -------------^ C:\practice\practice.f90(220) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(in) :: force(3) -------------^ C:\practice\practice.f90(221) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(inout) :: grad(ndim) -------------^ C:\practice\practice.f90(250) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(inout) :: x(ndim) -------------^ C:\practice\practice.f90(251) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(out) :: f, g(ndim) -------------^ C:\practice\practice.f90(255) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), intent(in) :: tol -------------^ C:\practice\practice.f90(257) : Error: A kind type parameter must be a compile-time constant. [DP] real(dp), parameter :: alpha = 1.0e-4_dp, sigma = 0.9_dp -------------^ (316) : Severe: Too many errors, exiting Error executing df.exe. practice.exe - 30 error(s), 0 warning(s) ,我的Fortran是2006年的版本的
11-02
你遇到的错误是因为你的 Fortran 编译器 **不支持 `iso_fortran_env` 模块**,并且无法识别 `real64` 这种现代 Fortran 的 kind 类型定义。这通常意味着你使用的是一个非常老的编译器(如早期版本的 Digital Fortran、Compaq Visual Fortran 或某些 2006 年前后的旧版编译器),它只支持 **Fortran 90/95 标准**,而不支持 **Fortran 2003+** 中引入的 `iso_fortran_env`。 --- ### ✅ 错误原因分析 ```fortran use iso_fortran_env, only: dp => real64 ``` 这一行是 **Fortran 2003** 引入的标准模块,用于获取双精度实数类型(`real64`)。但你的编译器不认识这个模块,因此报错: > **Error in opening the Library module file. [ISO_FORTRAN_ENV]** 后续所有 `real(dp)` 都失败,因为 `dp` 没有被定义。 --- ## ✅ 解决方案:用传统方法定义双精度类型 我们可以通过 `kind` 参数手动定义双精度类型,替代 `iso_fortran_env`。 ### 🔁 修改方式如下: 将原来的: ```fortran use iso_fortran_env, only: dp => real64 ``` 替换为: ```fortran integer, parameter :: dp = selected_real_kind(15, 307) ``` 这会告诉编译器:“我需要至少 15 位有效数字指数范围至少到 10^307 的实数类型”,这在几乎所有平台上都对应 **双精度(64位)浮点数**。 --- ### ✅ 修改后的完整兼容性代码(适用于 Fortran 90/95 编译器) ```fortran program Na3Cl2_Optimization implicit none ! 使用 selected_real_kind 定义双精度类型(兼容老编译器) integer, parameter :: dp = selected_real_kind(15, 307) integer, parameter :: n = 5 ! Na3Cl2: total 5 particles integer, parameter :: ndim = 3*n - 6 ! 3*5 - 6 = 9 independent variables real(dp), parameter :: V0 = 1090.0_dp ! eV real(dp), parameter :: r0 = 0.321_dp ! Å real(dp), parameter :: b = 1.0_dp ! eV real(dp), parameter :: c = 0.1_dp ! Å real(dp) :: x(3,n), x_reduced(ndim), grad(ndim) real(dp) :: energy, tol integer :: iter, max_iter logical :: converged ! 初始化猜测结构 (随机初始值,在 Å 单位下) call random_number(x_reduced) x_reduced = x_reduced * 4.0_dp - 2.0_dp ! [-2, 2] Å 初始分布 ! 设置收敛容差最大迭代次数 tol = 1.0e-6_dp max_iter = 1000 ! BFGS 优化主循环 call bfgs_optimize(x_reduced, energy, grad, converged, iter, max_iter, tol) ! 将 reduced 坐标映射回完整 3D 结构 call map_to_full_geometry(x_reduced, x) ! 输出最终结构 print '("# Final optimized geometry of Na3Cl2 (in Å)")' print '("# Particle types: Na+, Na+, Na+, Cl-, Cl-")' print '("# Index X Y Z")' do i = 1, n print '(I6, 3F10.5)', i, x(1,i), x(2,i), x(3,i) end do ! 检查是否为三维结构 if (any(abs(x(3,:)) > 1.0e-5_dp)) then print '("# The structure is THREE-DIMENSIONAL.")' else print '("# The structure is planar (2D).")' end if contains !==================================================== ! 映射 reduced 变量到完整 3D 坐标 (施加约束) !==================================================== subroutine map_to_full_geometry(x_red, x_full) real(dp), intent(in) :: x_red(ndim) real(dp), intent(out) :: x_full(3,n) integer :: idx x_full = 0.0_dp ! Particle 1: fixed at origin x_full(1,1) = 0.0_dp x_full(2,1) = 0.0_dp x_full(3,1) = 0.0_dp ! Particle 2: on x-axis x_full(1,2) = x_red(1) x_full(2,2) = 0.0_dp x_full(3,2) = 0.0_dp ! Particle 3: in xy-plane x_full(1,3) = x_red(2) x_full(2,3) = x_red(3) x_full(3,3) = 0.0_dp ! Particle 4: free idx = 4 x_full(1,4) = x_red(idx) x_full(2,4) = x_red(idx+1) x_full(3,4) = x_red(idx+2) ! Particle 5: free idx = 7 x_full(1,5) = x_red(idx) x_full(2,5) = x_red(idx+1) x_full(3,5) = x_red(idx+2) end subroutine map_to_full_geometry !==================================================== ! 计算总势能与梯度 !==================================================== subroutine compute_energy_and_gradient(x_red, e, g) real(dp), intent(in) :: x_red(ndim) real(dp), intent(out) :: e, g(ndim) real(dp) :: x_full(3,n), dx(3), r_ij, dVdr integer :: i, j, type_i, type_j real(dp) :: eta, delta, att_term, rep_term g = 0.0_dp e = 0.0_dp call map_to_full_geometry(x_red, x_full) do i = 1, n do j = i+1, n dx = x_full(:,i) - x_full(:,j) r_ij = sqrt(dot_product(dx, dx)) if (r_ij < 1.0e-8_dp) r_ij = 1.0e-8_dp ! 判断电荷类型: 前3个是 Na+, 后2个是 Cl- type_i = merge(1, 0, i <= 3) type_j = merge(1, 0, j <= 3) if (type_i /= type_j) then eta = -1.0_dp delta = 1.0_dp else eta = 1.0_dp delta = 0.0_dp end if ! 吸引项: eta * V0/r * exp(-r/r0) att_term = eta * V0 / r_ij * exp(-r_ij / r0) ! 排斥项: delta * b * (c/r)^12 rep_term = delta * b * (c / r_ij)**12 e = e + att_term + rep_term ! 导数 dV/dr dVdr = -eta * V0 / (r_ij*r_ij) * exp(-r_ij / r0) & + eta * V0 / r0 / r_ij * exp(-r_ij / r0) & - 12.0_dp * delta * b * (c**12) / (r_ij**13) dx = dx / r_ij ! 单位方向向量 call add_gradient_contribution(i, j, dVdr * dx, x_red, g) end do end do end subroutine compute_energy_and_gradient !==================================================== ! 添加梯度贡献 !==================================================== subroutine add_gradient_contribution(i, j, force_on_i, x_red, grad) integer, intent(in) :: i, j real(dp), intent(in) :: force_on_i(3), x_red(ndim) real(dp), intent(inout) :: grad(ndim) real(dp) :: x_full(3,n) call map_to_full_geometry(x_red, x_full) call accumulate_particle_grad(i, force_on_i, grad) call accumulate_particle_grad(j, -force_on_i, grad) end subroutine add_gradient_contribution !==================================================== ! 累加某个粒子上的梯度 !==================================================== subroutine accumulate_particle_grad(ipart, force, grad) integer, intent(in) :: ipart real(dp), intent(in) :: force(3) real(dp), intent(inout) :: grad(ndim) select case (ipart) case (1) return ! fixed case (2) grad(1) = grad(1) + force(1) case (3) grad(2) = grad(2) + force(1) grad(3) = grad(3) + force(2) case (4) grad(4:6) = grad(4:6) + force case (5) grad(7:9) = grad(7:9) + force end select end subroutine accumulate_particle_grad !==================================================== ! BFGS 优化器(简化版) !==================================================== subroutine bfgs_optimize(x, f, g, conv, iter, max_iter, tol) real(dp), intent(inout) :: x(ndim) real(dp), intent(out) :: f, g(ndim) logical, intent(out) :: conv integer, intent(out) :: iter integer, intent(in) :: max_iter real(dp), intent(in) :: tol real(dp), parameter :: alpha = 1.0e-4_dp, sigma = 0.9_dp real(dp) :: H(ndim,ndim), s(ndim), y(ndim), rho, sy, step_len real(dp) :: x_prev(ndim), g_prev(ndim), fx, gx, dg(ndim) integer :: k, i call compute_energy_and_gradient(x, f, g) conv = .false. ! 初始化逆 Hessian 为单位阵 H = 0.0_dp do i = 1, ndim H(i,i) = 1.0_dp end do do k = 1, max_iter if (sqrt(dot_product(g, g)) < tol) then conv = .true. exit end if s = -matmul(H, g) ! 简单线搜索 step_len = 1.0_dp x_prev = x fx = f gx = dot_product(g, s) do while (step_len > 1.0e-10_dp) x = x_prev + step_len * s call compute_energy_and_gradient(x, f, g) if (f <= fx + alpha * step_len * gx) exit step_len = step_len * sigma end do s = x - x_prev call compute_energy_and_gradient(x_prev, fx, g_prev) y = g - g_prev sy = dot_product(y, s) if (abs(sy) > 1.0e-12_dp) then rho = 1.0_dp / sy ! 简化 BFGS 更新(可进一步优化) H = H + rho**2 * matmul(matmul(H, y), transpose(matmul(H, y))) * dot_product(s, s) & - rho * (spread(matmul(H,y), dim=2, ncopies=ndim) * spread(s, dim=1, ncopies=ndim) + & spread(s, dim=2, ncopies=ndim) * spread(matmul(H,y), dim=1, ncopies=ndim)) end if end do iter = k if (.not. conv) then print '("# Warning: BFGS did not converge in ", I0, " iterations.")', max_iter end if end subroutine bfgs_optimize end program Na3Cl2_Optimization ``` --- ### ✅ 如何编译(命令行示例) 如果你使用的是 **gfortran**(推荐升级!),可以这样编译: ```bash gfortran -o practice.exe practice.f90 ``` 即使你的编译器较老,只要支持 `selected_real_kind` `merge` 函数(大多数 Fortran 95 编译器都支持),上面代码就能运行。 --- ### 🛠️ 如果你还遇到问题,请检查以下几点: | 问题 | 解决方法 | |------|---------| | `random_number` 不支持? | 替换为 `call srand()` + `rand()`(非标准)或初始化为固定值 | | `merge` 函数不支持? | 改用 `if-else` 判断 | | `matmul` 报错? | 确保数组维度正确,或降维测试 | --- ### ✅ 总结 你现在使用的 Fortran 编译器太老,不支持 `iso_fortran_env`。解决方案是: ✅ **用 `selected_real_kind(15,307)` 替代 `real64`** ```fortran integer, parameter :: dp = selected_real_kind(15, 307) ``` 这样即可在 **Fortran 90/95 兼容环境**中使用双精度浮点数,无需 `iso_fortran_env`。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值