Prove termination of (optimized) diff algorithm implementation - #37
Merged
Conversation
Contributor
ninioArtillero
force-pushed
the
xg/ses-termination
branch
2 times, most recently
from
August 6, 2026 21:48
e5cb8c3 to
34f28ed
Compare
ninioArtillero
force-pushed
the
xg/ses-termination
branch
from
August 7, 2026 19:54
e7d2434 to
2f99dfb
Compare
ninioArtillero
force-pushed
the
xg/ses-termination
branch
3 times, most recently
from
August 10, 2026 19:38
5b0b7b7 to
cd35bf8
Compare
ninioArtillero
marked this pull request as ready for review
August 10, 2026 19:38
ninioArtillero
force-pushed
the
xg/ses-termination
branch
from
August 11, 2026 01:07
cd35bf8 to
d1f3ec5
Compare
Contributor
|
Very nice \o/ |
ninioArtillero
force-pushed
the
xg/ses-termination
branch
from
August 11, 2026 16:50
d1f3ec5 to
d8a092c
Compare
facundominguez
force-pushed
the
xg/ses-termination
branch
2 times, most recently
from
August 12, 2026 12:13
4acf270 to
fb216f2
Compare
The manhattan distance from a node towards the algorithm's end point (lena, lenb) is used as termination metric for `addsnake`. Phantom parameters for both input lengths are introduced to provide them as arguments to the metric. The diagonal predicate `DiagPred` is a refinement type alias encoding the condition of an equality predicate (such as `canDiag`) to enter the recursive call inside `addsnake`. This allows Liquid Haskell to know that both coordinates are smaller than its corresponding input length, those fulfilling `manhattanDistance` preconditions. `dstep` is extended to provide the required phantom parameters and is temporarily ignored because proving node coordinates in a wave front are within bounds (`manhatanDistance` preconditions) requires discarting out-of-bounds nodes. This is implemented as an optimization in a following commit. Co-authored-by: Facundo Domínguez <facundominguez@gmail.com>
Co-authored-by: Facundo Domínguez <facundominguez@gmail.com>
`dstep` is optimized by adding checks that prevent the production of out-of-bound nodes (those that would exit the edit grid). This solves the problem `getDiffBy` empty list equations where intended to address in seereason@33bf8bc which are removed to avoid unnecessary complexity. Adds a source note describing the design choice to leave aside a (small) possible optimization in favor of reduced specification complexity. Co-authored-by: Facundo Domínguez <facundominguez@gmail.com>
A `_wfDistanceToGoal` function is defined to be used as termination metric for `ses`. In this commit, `dstep` is specified and checked to reduce it from input to output. The wave front diagonal condition is changed to look at the head of the node list instead of the diagonal edit distance parameter, and its nodes are specified to be within bounds. Indeed, now that wave fronts are trimmed down to be within bounds, we can no longer guarantee that the first node's diagonal matches the edit distance. The `stepAndMerge` specification is strengthen to preserve this new variant of wave front diagonal invariant: With the current optimization of `dstep`, wave fronts don't necessarily grow, but the 2-step specing is preserved. Its postconditions stating that it decreases the measure if the result is non-empty are split--(A -> B) and (A -> C) instead of (A -> B && C)-- for better error reporting from LH. `furthestReaching` is now reflected to simplify its postconditions, and a precondition to check that edit trace of the compared nodes match is added. Co-authored-by: Facundo Domínguez <facundominguez@gmail.com>
ninioArtillero
force-pushed
the
xg/ses-termination
branch
from
August 12, 2026 15:41
05e9b60 to
f6c6487
Compare
The implementation of `ses` changes from a `dropWhile` driven search for the algorithm's end point in a lazy stream composed of all wave front nodes, to the explicit recursion of a wave-front wise search for such end point. This change was designed to allow a termination proof using Liquid Haskell: by inspecting each wave front separately, instead of all concatenated together in an infinite stream, we can define a wave front metric as its minimum distance to the endpoint and show it is reduced by the recursive calls (to `dstep`). Performance-wise, we get a small optimization of the benchmark of ~12%
Co-authored-by: Facundo Domínguez <facundominguez@gmail.com>
`_kdiag` is reflected too, so that it can be unfolded by PLE when used in definition of `_wfDiags`.
ninioArtillero
force-pushed
the
xg/ses-termination
branch
from
August 12, 2026 15:43
f6c6487 to
733f983
Compare
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds an static check for the
ses(smallest edit script) function termination by defining a wave front's distance to the endpoint as decreasing metric. This function is the implementation's entry point to the Myers diff algorithm, whose original specification waives the need for a termination proof by bounding the algorithm's outer loop by the known worst case edit script length (i.e. the sum of both input's lengths). The implicit termination proof contained in this check provides a compeling argument for the correctness of the implementation by stablishing a link with its original specification.The check depends on:
dstep(2825ba1) andses(572638c ) functions. The former resulting in a 4x performance improvement that required a new benchmark comparing inputs with a bigger size difference (607c8d3) to be noticed.dstepandaddsnake(8eba64a) to thread a notion of the edit grid within the call stackRefactorings
The "iterate, flatten and find" steps of
sesare merged in a single "search" step. Formerly, the search for the endpoint was done usingdropWhile (\dl -> poi dl /= lena || poj dl /= lenb)on a single infinite stream containing all nodes, from succesive iterations of the wave front, in order. Now, theseach :: Int -> [DL] -> [DI]does the same thing, inspecting one wave front iteration at a time, allowing Liquid Haskell to reason about the incremental reach of the wave front to the goal. Benchmarks (cabal bench) shows a negligeble performance gain on this change alone when tested on a local branch. This is still the case after thedstepoptimization; before and after benchmark results are shown below.The
dstepoptimization consist (informally) of narrowing the wave front to in-bound nodes only: checks are placed to drop non-competing and out-of-bound nodes. The performance gain on the existing benchmark is barely noticeable. However, the new benchmark comparing inputs differing noticeably in size shows the gain to be in the order of 4x for this case. Incidentally, this addresses #1 robustly for all disparities between input sizes, which the additional equations togetDiffByadded in #28 solved for the specific case of an input of size 0; these equations are removed to avoid the unnecessary complexity of this ad hoc case.This refactoring is instrumental to prove
dstepreduceswfDistanceToGoal(6590d8e), providing guarantee that themanhattanDistanceis non-negative on all wave front nodes. Also, the previously localhStepandvStepfunctions where moved to the top level in toreflectthem (bc324f5), making their unfoldings available in the refinement logic, in order to reduce the specification load: otherwise, they incur in substantive specifications of their own (as found before this change in 6590d8e). For details on how this refactoring is related to the wave front specification see[NOTE: diagonal-invariant].Benchmarks
Before
dsteprefactoring (on 607c8d3)After
dsteprefactoring, but beforesesrefactoring (on 6590d8e)After both refactorings (on 52f18d4):
Phantom parameters and Liquid Haskell supplementary functions.
The new phantom parameters in
dstepandaddsnakecarrying both input lengths are used to pass these values to the supplementary functions defined under theProving the Algorithm's Termination in LiquidHaskellcode section at the bottom ofDiff.hs.LiquidHaskell specific machinery leading to the definition of the
WaveFrontrefinement type alias (introduced in #32) is moved to the new code sectionLiquidHaskell Wave Front Specification, also near the bottom of the file. TheDLNalias is modified to take parameters for the input lengths, and a local invariant for theDLtype is added in ausingannotation to make the positivity of its coordinates available in contexts where their values where not pattern matched.