Ada Tutorial(3)SPARK2——Post condition + Loop Invariant 后置条件 + 循环不变量

文章探讨了在SPARK编程环境中,如何通过添加后置条件和循环不变量来验证DivMod算法的正确性。当尝试证明X=K*N+R且R<N的断言时,发现因缺少DivMod过程的合同(预/后条件注解),导致SPARKprover无法证明。解决方案是为DivMod过程添加适当的后置条件,然后通过引入循环不变量来帮助证明循环不会导致整数溢出,并确保程序的正确性。

divmod

-- divmod.adb
package body DivMod with SPARK_Mode is

   procedure DivMod(X : Positive; N : Positive; K : out Natural; Remainder : out Natural)
   is
      Y : Natural := X;
   begin
      K := 0;
      while Y >= N loop
         Y := Y - N;
         K := K + 1;
      end loop;
      Remainder := Y;
   end DivMod;


end DivMod;
--divmod.ads

package DivMod with SPARK_Mode is

   procedure DivMod(X : in Positive; N : in Positive; K : out Natural;
                    Remainder : out Natural);

end DivMod;
--main.adb

with Ada.Integer_Text_IO;
use Ada.Integer_Text_IO;
with Ada.Text_IO;
use Ada.Text_IO;
with DivMod;


procedure Main with SPARK_Mode is
   K : Natural;
   R : Natural;
   X : Integer;
   N : Integer;
begin
   Put_Line("Compute the result and remainder of X/N, via repeated subtraction.");
   Put_Line("The result and remainder are numbers K and R where X = K * N + R and R < N");
   Put("Enter a positive integer for X: ");
   Get(X);
   Put("Enter a positive integer for N: ");
   Get(N);
   if (X > 0 and N > 0) then
      DivMod.DivMod(X,N,K,R);
      Put("K: "); Put(K); New_Line;
      Put("R: "); Put(R); New_Line;

      -- assert that the result is correct
      pragma Assert (X = K * N + R and R < N);

      -- SPARK needs some help to conclude that X/N = K.
      -- We derive that final result step-by-step

      -- firstly K is not greater than X/N
      pragma Assert (K * N <= X);
      pragma Assert (X/N >= K);

      -- secondly K is not lower than X/N
      pragma Assert (if K < Integer'Last and then Integer'Last / N > K + 1 then
                        N * (K + 1) > X);
      pragma Assert (X/N <= K);

      -- therefore K is exactly equal to X/N
      pragma Assert (X/N = K);
   else
      Put_Line("X and N must both be positive. Exiting.");
   end if;
end Main;

问题:Now run the SPARK prover: SPARK → Prove All. You will see that the provided main.adb contains a number of pragma Assert statements. These are assertions that the SPARK prover tries to prove always hold.
You will see that the assertion X = K * N + R and R < N in main.adb cannot be proved. (You
may also see potential problems reported in the DivMod package, but we will come to those later.)
This is because the DivMod procedure has no contract (pre/postcondition annotations), so the SPARK prover cannot tell anything about K and R after it is called.
在这里插入图片描述
问题:Add a postcondition annotation to the DivMod procedure in divmod.ads to allow the failing assert to be proved. Hint: this postcondition should state what is true about K and R in terms of X and N, after DivMod returns.

  • 上述问题的原因就是:我们在 main.adb 中使用了 assert,但是由于在 divmod.ads 中我们没有使用 post 来向程序发布 X = K * N + Remainder 这个关系,所以在 main.adb 中经过 assert 的时候就没法满足,因此我们只需要在 divmod.adb 中加入这个 post condition 即可
package DivMod with SPARK_Mode is
   procedure DivMod(X : in Positive; N : in Positive; K : out Natural;
                    Remainder : out Natural) with
   Post=> (X = K * N + Remainder);
end DivMod;

在这里插入图片描述

  • 上述问题解决

  • 你可以将 post condition 的作用看成:向程序显示地声明某个 procedure 或者 function 的执行结果,以便 SPARK 进行检查

问题: Now run the SPARK Prover again. Now the assertions in main.adb should be able to be proved, using the contract on DivMod. However, the SPARK prover cannot actually prove that the contract holds.
It also cannot prove that the loop in DivMod won’t cause integer overflow.
在这里插入图片描述

To help it prove these, we need to add a suitable loop invariant annotation for the while-loop in DivMod. To work out what the invariant should say, you can add print statements to this loop to get it to print out the values of Y and K each time through the loop. Then look for a relationship that always holds between Y, K, N and X.
Once you have figured out the invariant, add an appropriate annotation to the while-loop: pragma Loop Invariant (. . . your invariant goes here . . .);

package body DivMod with SPARK_Mode is
   procedure DivMod(X : Positive; N : Positive; K : out Natural; Remainder : out Natural)
   is
      Y : Natural := X;
   begin
      K := 0;
      while Y >= N loop
         Y := Y - N;
         K := K + 1;
         pragma Loop_Invariant (Y <= X);
         pragma Loop_Invariant (Y + N * K = X);
      end loop;
      Remainder := Y;
   end DivMod;
end DivMod;

循环不变量 v.s. 后置条件

循环不变量和后置条件(postcondition)都是用于验证程序正确性的关键工具,但它们在具体用途上有一些区别。

  • 循环不变量: 这是一个在循环的每一次迭代开始和结束时都保持为真的条件。循环不变量是在循环的过程中不断保持的一个条件或属性,它可以帮助我们理解循环的行为,保证循环的正确性。循环不变量通常会设计为捕获关于正在进行的计算的一些关键信息。
  • 后置条件(postcondition): 这是一个过程或函数结束时必须满足的条件。它描述了程序在执行后的预期状态。后置条件通常与前置条件(precondition,即程序开始前的状态)以及程序的实际操作一起使用,以证明程序的正确性。

在某种程度上,你可以认为循环不变量在循环的上下文中类似于后置条件, 因为它描述了每次循环迭代结束时的预期状态。但是,它们在语义上是不同的:后置条件描述的是程序结束时的状态,而循环不变量描述的是循环的每次迭代。

在形式化方法和程序验证中,通常会同时使用循环不变量和前置/后置条件,以帮助保证程序的正确性。

Now re-run the SPARK prover. If your invariant is correct, you should find that the SPARK prover does not report any problems. You have proved the correctness of your first program. Congratulations!

扩展思考

问题: If you have time: Look at the assert statements in main.adb more closely. The final one asserts that X / N = K, i.e. that K does in fact hold the result of performing integer division on X by N.
Try commenting out each of the assert statements above and re-running the SPARK prover for each. You should find that when one of these assertions is commented out, one of the following assertions cannot be proved.
This means that, to prove that following assertion, the SPARK prover first needs to know that the preceding one holds, i.e. it cannot derive the following assertion in one go but it needs some help: we first have to tell it to derive the intermediate assertion and, only then, can it derive the subsequent one. This can sometimes happen with automated provers like the SPARK prover. Using intermediate assertions like this can be a useful way, therefore, helping to derive extra facts that cannot be inferred automatically.

【ACDC微电网的能源管理策略】微电网仿真模型包括光伏发电机、燃料电池系统、超级电容器和直流侧的电池,包括电压源变换器(VSC),用于将微电网的直流侧与交流侧相连接Simulink仿真实现内容概要:本文介绍了一个用于ACDC微电网能源管理策略研究的Simulink仿真模型,该模型集成了光伏发电机、燃料电池系统、超级电容器和直流侧电池等多种分布式能源与储能装置,并通过电压源变换器(VSC)实现微电网直流侧与交流侧的连接。文档重点在于构建包含多能源协调控制与能量管理策略的仿真系统,可用于研究微电网在并网与孤岛模式下的稳定运行、能量优化分配及动态响应特性,体现了对复杂电力电子接口与多源协同控制的建模能力。; 适合人群:电力系统、新能源发电、微电网控制及相关领域的科研人员与工程技术人员,具备一定的MATLAB/Simulink仿真基础和电力电子知识背景;研究生及高年级本科生亦可参考学习。; 使用场景及目标:①开展微电网多能源系统建模与仿真验证;②研究VSC在交直流互联中的控制策略;③设计与优化微电网能量管理策略(EMS);④支持科研项目、课程设计或毕业设计中的系统仿真环节。; 阅读建议:建议结合MATLAB/Simulink环境实际操作,逐步搭建模型并调试各组件参数,重点关注VSC控制逻辑与储能协调机制的设计,同时可拓展加入更多智能优化算法(如PSO、MPC)实现高级能量管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暖仔会飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值