Loop invariant

Last updated

In computer science, a loop invariant is a property of a program loop that is true before (and after) each iteration. It is a logical assertion, sometimes checked with a code assertion. Knowing its invariant(s) is essential in understanding the effect of a loop.

Contents

In formal program verification, particularly the Floyd-Hoare approach, loop invariants are expressed by formal predicate logic and used to prove properties of loops and by extension algorithms that employ loops (usually correctness properties). The loop invariants will be true on entry into a loop and following each iteration, so that on exit from the loop both the loop invariants and the loop termination condition can be guaranteed.

From a programming methodology viewpoint, the loop invariant can be viewed as a more abstract specification of the loop, which characterizes the deeper purpose of the loop beyond the details of this implementation. A survey article [1] covers fundamental algorithms from many areas of computer science (searching, sorting, optimization, arithmetic etc.), characterizing each of them from the viewpoint of its invariant.

Because of the similarity of loops and recursive programs, proving partial correctness of loops with invariants is very similar to proving the correctness of recursive programs via induction. In fact, the loop invariant is often the same as the inductive hypothesis to be proved for a recursive program equivalent to a given loop.

Informal example

The following C subroutine max() returns the maximum value in its argument array a[], provided its length n is at least 1. Comments are provided at lines 3, 6, 9, 11, and 13. Each comment makes an assertion about the values of one or more variables at that stage of the function. The highlighted assertions within the loop body, at the beginning and end of the loop (lines 6 and 11), are exactly the same. They thus describe an invariant property of the loop. When line 13 is reached, this invariant still holds, and it is known that the loop condition i!=n from line 5 has become false. Both properties together imply that m equals the maximum value in a[0...n-1], that is, that the correct value is returned from line 14.

intmax(intn,constinta[]){intm=a[0];// m equals the maximum value in a[0...0]inti=1;while(i!=n){// m equals the maximum value in a[0...i-1]if(m<a[i])m=a[i];// m equals the maximum value in a[0...i]++i;// m equals the maximum value in a[0...i-1]}// m equals the maximum value in a[0...i-1], and i==nreturnm;}

Following a defensive programming paradigm, the loop condition i!=n in line 5 should better be modified to i<n, in order to avoid endless looping for illegitimate negative values of n. While this change in code intuitively shouldn't make a difference, the reasoning leading to its correctness becomes somewhat more complicated, since then only i>=n is known in line 13. In order to obtain that also i<=n holds, that condition has to be included into the loop invariant. It is easy to see that i<=n, too, is an invariant of the loop, since i<n in line 6 can be obtained from the (modified) loop condition in line 5, and hence i<=n holds in line 11 after i has been incremented in line 10. However, when loop invariants have to be manually provided for formal program verification, such intuitively too obvious properties like i<=n are often overlooked.

Floyd–Hoare logic

In Floyd–Hoare logic, [2] [3] the partial correctness of a while loop is governed by the following rule of inference:

This means:

In other words: The rule above is a deductive step that has as its premise the Hoare triple . This triple is actually a relation on machine states. It holds whenever starting from a state in which the boolean expression is true and successfully executing some code called , the machine ends up in a state in which I is true. If this relation can be proven, the rule then allows us to conclude that successful execution of the program will lead from a state in which I is true to a state in which holds. The boolean formula I in this rule is called a loop invariant.

With some variations in the notation used, and with the premise that the loop halts, this rule is also known as the Invariant Relation Theorem. [4] [5] As one 1970s textbook presents it in a way meant to be accessible to student programmers: [4]

Let the notation P { seq } Q mean that if P is true before the sequence of statements seq run, then Q is true after it. Then the invariant relation theorem holds that

P & c { seq } P
implies
P { DO WHILE (c); seq END; } P & ¬c

Example

The following example illustrates how this rule works. Consider the program

while (x < 10)     x := x+1;

One can then prove the following Hoare triple:

The condition C of the while loop is . A useful loop invariant I has to be guessed; it will turn out that is appropriate. Under these assumptions it is possible to prove the following Hoare triple:

While this triple can be derived formally from the rules of Floyd-Hoare logic governing assignment, it is also intuitively justified: Computation starts in a state where is true, which means simply that is true. The computation adds 1 to x, which means that is still true (for integer x).

Under this premise, the rule for while loops permits the following conclusion:

However, the post-condition (x is less than or equal to 10, but it is not less than 10) is logically equivalent to , which is what we wanted to show.

The property is another invariant of the example loop, and the trivial property is another one. Applying the above inference rule to the former invariant yields . Applying it to invariant yields , which is slightly more expressive.

Programming language support

Eiffel

The Eiffel programming language provides native support for loop invariants. [6] A loop invariant is expressed with the same syntax used for a class invariant. In the sample below, the loop invariant expression x <= 10 must be true following the loop initialization, and after each execution of the loop body; this is checked at runtime.

fromx:=0invariantx<=10untilx>10loopx:=x+1end

Whiley

The Whiley programming language also provides first-class support for loop invariants. [7] Loop invariants are expressed using one or more where clauses, as the following illustrates:

functionmax(int[]items)->(intr)// Requires at least one element to compute maxrequires|items|>0// (1) Result is not smaller than any elementensuresall{iin0..|items||items[i]<=r}// (2) Result matches at least one elementensuressome{iin0..|items||items[i]==r}://nati=1intm=items[0]//whilei<|items|// (1) No item seen so far is larger than mwhereall{kin0..i|items[k]<=m}// (2) One or more items seen so far matches mwheresome{kin0..i|items[k]==m}:ifitems[i]>m:m=items[i]i=i+1//returnm

The max() function determines the largest element in an integer array. For this to be defined, the array must contain at least one element. The postconditions of max() require that the returned value is: (1) not smaller than any element; and, (2) that it matches at least one element. The loop invariant is defined inductively through two where clauses, each of which corresponds to a clause in the postcondition. The fundamental difference is that each clause of the loop invariant identifies the result as being correct up to the current element i, whilst the postconditions identify the result as being correct for all elements.

Use of loop invariants

A loop invariant can serve one of the following purposes:

  1. purely documentary
  2. to be checked within in the code, e.g. by an assertion call
  3. to be verified based on the Floyd-Hoare approach

For 1., a natural language comment (like // m equals the maximum value in a[0...i-1] in the above example) is sufficient.

For 2., programming language support is required, such as the C library assert.h, or the above-shown invariant clause in Eiffel. Often, run-time checking can be switched on (for debugging runs) and off (for production runs) by a compiler or a runtime option.[ citation needed ]

For 3., some tools exist to support mathematical proofs, usually based on the above-shown Floyd–Hoare rule, that a given loop code in fact satisfies a given (set of) loop invariant(s).

The technique of abstract interpretation can be used to detect loop invariant of given code automatically. However, this approach is limited to very simple invariants (such as 0<=i && i<=n && i%2==0).

Distinction from loop-invariant code

Loop-invariant code consists of statements or expressions that can be moved outside a loop body without affecting the program semantics. Such transformations, called loop-invariant code motion, are performed by some compilers to optimize programs. A loop-invariant code example (in the C programming language) is

for(inti=0;i<n;++i){x=y+z;a[i]=6*i+x*x;}

where the calculations x = y+z and x*x can be moved before the loop, resulting in an equivalent, but faster, program:

x=y+z;t1=x*x;for(inti=0;i<n;++i){a[i]=6*i+t1;}

In contrast, e.g. the property 0<=i && i<=n is a loop invariant for both the original and the optimized program, but is not part of the code, hence it doesn't make sense to speak of "moving it out of the loop".

Loop-invariant code may induce a corresponding loop-invariant property.[ clarification needed ] For the above example, the easiest way to see it is to consider a program where the loop invariant code is computed both before and within the loop:

x1=y+z;t1=x1*x1;for(inti=0;i<n;++i){x2=y+z;a[i]=6*i+t1;}

A loop-invariant property of this code is (x1==x2 && t1==x2*x2) || i==0, indicating that the values computed before the loop agree with those computed within (except before the first iteration).

See also

Related Research Articles

<span class="mw-page-title-main">Entropy (information theory)</span> Expected amount of information needed to specify the output of a stochastic data source

In information theory, the entropy of a random variable is the average level of "information", "surprise", or "uncertainty" inherent to the variable's possible outcomes. Given a discrete random variable , which takes values in the alphabet and is distributed according to :

In computability theory, a primitive recursive function is, roughly speaking, a function that can be computed by a computer program whose loops are all "for" loops. Primitive recursive functions form a strict subset of those general recursive functions that are also total functions.

<span class="mw-page-title-main">Wiener process</span> Stochastic process generalizing Brownian motion

In mathematics, the Wiener process is a real-valued continuous-time stochastic process named in honor of American mathematician Norbert Wiener for his investigations on the mathematical properties of the one-dimensional Brownian motion. It is often also called Brownian motion due to its historical connection with the physical process of the same name originally observed by Scottish botanist Robert Brown. It is one of the best known Lévy processes and occurs frequently in pure and applied mathematics, economics, quantitative finance, evolutionary biology, and physics.

Hoare logic is a formal system with a set of logical rules for reasoning rigorously about the correctness of computer programs. It was proposed in 1969 by the British computer scientist and logician Tony Hoare, and subsequently refined by Hoare and other researchers. The original ideas were seeded by the work of Robert W. Floyd, who had published a similar system for flowcharts.

In programming language theory, subtyping is a form of type polymorphism in which a subtype is a datatype that is related to another datatype by some notion of substitutability, meaning that program elements, typically subroutines or functions, written to operate on elements of the supertype can also operate on elements of the subtype. If S is a subtype of T, the subtyping relation means that any term of type S can safely be used in any context where a term of type T is expected. The precise semantics of subtyping here crucially depends on the particulars of how "safely be used" and "any context" are defined by a given type formalism or programming language. The type system of a programming language essentially defines its own subtyping relation, which may well be trivial, should the language support no conversion mechanisms.

In mathematics, a Heyting algebra (also known as pseudo-Boolean algebra) is a bounded lattice (with join and meet operations written ∨ and ∧ and with least element 0 and greatest element 1) equipped with a binary operation ab of implication such that (ca) ≤ b is equivalent to c ≤ (ab). From a logical standpoint, AB is by this definition the weakest proposition for which modus ponens, the inference rule AB, AB, is sound. Like Boolean algebras, Heyting algebras form a variety axiomatizable with finitely many equations. Heyting algebras were introduced by Arend Heyting (1930) to formalize intuitionistic logic.

<span class="mw-page-title-main">Invariant (mathematics)</span> Property that is not changed by mathematical transformations

In mathematics, an invariant is a property of a mathematical object which remains unchanged after operations or transformations of a certain type are applied to the objects. The particular class of objects and type of transformations are usually indicated by the context in which the term is used. For example, the area of a triangle is an invariant with respect to isometries of the Euclidean plane. The phrases "invariant under" and "invariant to" a transformation are both used. More generally, an invariant with respect to an equivalence relation is a property that is constant on each equivalence class.

In probability theory and mathematical physics, a random matrix is a matrix-valued random variable—that is, a matrix in which some or all elements are random variables. Many important properties of physical systems can be represented mathematically as matrix problems. For example, the thermal conductivity of a lattice can be computed from the dynamical matrix of the particle-particle interactions within the lattice.

<span class="mw-page-title-main">Systolic geometry</span> Form of differential geometry

In mathematics, systolic geometry is the study of systolic invariants of manifolds and polyhedra, as initially conceived by Charles Loewner and developed by Mikhail Gromov, Michael Freedman, Peter Sarnak, Mikhail Katz, Larry Guth, and others, in its arithmetical, ergodic, and topological manifestations. See also a slower-paced Introduction to systolic geometry.

In number theory, a narcissistic number in a given number base is a number that is the sum of its own digits each raised to the power of the number of digits.

In mathematics and computing, universal hashing refers to selecting a hash function at random from a family of hash functions with a certain mathematical property. This guarantees a low number of collisions in expectation, even if the data is chosen by an adversary. Many universal families are known, and their evaluation is often very efficient. Universal hashing has numerous uses in computer science, for example in implementations of hash tables, randomized algorithms, and cryptography.

In control theory, the linear–quadratic–Gaussian (LQG) control problem is one of the most fundamental optimal control problems, and it can also be operated repeatedly for model predictive control. It concerns linear systems driven by additive white Gaussian noise. The problem is to determine an output feedback law that is optimal in the sense of minimizing the expected value of a quadratic cost criterion. Output measurements are assumed to be corrupted by Gaussian noise and the initial state, likewise, is assumed to be a Gaussian random vector.

In mathematics, the Lebesgue differentiation theorem is a theorem of real analysis, which states that for almost every point, the value of an integrable function is the limit of infinitesimal averages taken about the point. The theorem is named for Henri Lebesgue.

In mathematics, specifically in the study of ordinary differential equations, the Peano existence theorem, Peano theorem or Cauchy–Peano theorem, named after Giuseppe Peano and Augustin-Louis Cauchy, is a fundamental theorem which guarantees the existence of solutions to certain initial value problems.

<span class="mw-page-title-main">Pu's inequality</span>

In differential geometry, Pu's inequality, proved by Pao Ming Pu, relates the area of an arbitrary Riemannian surface homeomorphic to the real projective plane with the lengths of the closed curves contained in it.

In mathematics – specifically, in stochastic analysis – an Itô diffusion is a solution to a specific type of stochastic differential equation. That equation is similar to the Langevin equation used in physics to describe the Brownian motion of a particle subjected to a potential in a viscous fluid. Itô diffusions are named after the Japanese mathematician Kiyosi Itô.

In computer science, a loop variant is a mathematical function defined on the state space of a computer program whose value is monotonically decreased with respect to a (strict) well-founded relation by the iteration of a while loop under some invariant conditions, thereby ensuring its termination. A loop variant whose range is restricted to the non-negative integers is also known as a bound function, because in this case it provides a trivial upper bound on the number of iterations of a loop before it terminates. However, a loop variant may be transfinite, and thus is not necessarily restricted to integer values.

A Hindley–Milner (HM) type system is a classical type system for the lambda calculus with parametric polymorphism. It is also known as Damas–Milner or Damas–Hindley–Milner. It was first described by J. Roger Hindley and later rediscovered by Robin Milner. Luis Damas contributed a close formal analysis and proof of the method in his PhD thesis.

Whiley is an experimental programming language that combines features from the functional and imperative paradigms, and supports formal specification through function preconditions, postconditions and loop invariants. The language uses flow-sensitive typing also known as "flow typing."

References

  1. Carlo A. Furia, Bertrand Meyer and Sergey Velder. "Loop invariants: analysis, classification, and examples."ACM Computing Surveys. vol. 46, no. 3, February 2014(
  2. Robert W. Floyd (1967). "Assigning Meanings to Programs" (PDF). In J.T. Schwartz (ed.). Proceedings of Symposia in Applied Mathematics. Mathematical Aspects of Computer Science. Vol. 19. Providence, RI: American Mathematical Society. pp. 19–32.
  3. Hoare, C. A. R. (October 1969). "An axiomatic basis for computer programming" (PDF). Communications of the ACM . 12 (10): 576–580. doi:10.1145/363235.363259. S2CID   207726175. Archived from the original (PDF) on 2016-03-04.
  4. 1 2 Conway, Richard; Gries, David (1973). An Introduction to Programming: A Structured Approach using PL/1 and PL/C. Cambridge, Massachusetts: Winthrop. pp. 198–200.
  5. Huang, J. C. (2009). Software Error Detection through Testing and Analysis. Hoboken, New Jersey: John Wiley & Sons. pp. 156–157.
  6. Meyer, Bertrand, Eiffel: The Language, Prentice Hall, 1991, pp. 129–131.
  7. Pearce, David J.; Groves, Lindsay (2015). "Designing a Verifying Compiler: Lessons Learned from Developing Whiley". Science of Computer Programming. 113: 191–220. doi:10.1016/j.scico.2015.09.006.

Further reading