Инвариант цикла Дафни, не поддерживаемый циклом

Моя задача — инициализировать матрицу 8x8 и убедиться, что все элементы внутри матрицы равны нулю. Я также должен использовать циклы while и инварианты циклов для реализации. Моя реализация выглядит так:

method initMatrix(a: array2<int>)
    modifies a
    // require an 8 x 8 matrix
    requires a.Length0 == 8 && a.Length1 == 8
    // Ensures that all rows in the matrix are zeroes
    ensures forall row, col :: 0 <= row < a.Length0 && 0 <= col < a.Length1 ==> a[row, col] == 0
{
  var row : nat := 0;
  while(row < a.Length0)
    invariant 0 <= row <= a.Length0
    invariant forall i,j :: 0 <= i < row && 0 <= j < a.Length1 ==> a[i,j] == 0
  {
    var col : nat := 0;
    while(col < a.Length1)
      invariant 0 <= row <= a.Length0
      invariant 0 <= col <= a.Length1
      invariant forall i,j :: i == row && 0 <= j < col ==> a[i,j] == 0
    {
        a[row, col] := 0;
        col := col + 1;
    }
    row := row + 1;
  }
}

Логически я думаю, что все мои квантификаторы верны. Однако Дафни считает, что мой цикл инвариантен во внешнем цикле while.

invariant forall i,j :: 0 <= i < row && 0 <= j < a.Length1 ==> a[i,j] == 0

не поддерживается циклом.

Сначала я заподозрил, что проблема существует в моей реализации. Поэтому я сначала запустил метод без каких-либо предварительных и последующих условий и напечатал все элементы массива; на выходе была матрица, полная нулей.

Чтобы быть вдвойне уверенным, что он заменяет все элементы, а не просто использует нули с момента создания экземпляра матрицы, я инициализировал все элементы в матрице единицами и распечатал все значения внутри нее. Результат был тот же - все элементы в матрице были 1s.

Из этого эксперимента я могу сделать вывод, что проблема не в моей реализации, а в том, как я определял свои спецификации.

Я прошел свой алгоритм вручную и обнаружил, что он логически должен работать, однако я думаю, что мое понимание того, как квантификаторы работают в инвариантах цикла в Dafny, может быть искажено.

Что именно происходит, когда условие 0 <= i < row && 0 <= j < a.Length1 ложно? На первой итерации переменная row равна 0, что не удовлетворяет условию 0 <= i < row. Проходит ли он первую строку и проверяет, все ли элементы в этой строке равны нулю, или пропускает ее?

Что происходит, когда он достигает оператора row := row + 1; на начальной итерации? Использует ли инвариант цикла invariant forall i,j :: 0 <= i < row && 0 <= j < a.Length1 ==> a[i,j] == 0 значение row при входе (которое равно нулю) или использует увеличенное значение row (1)?


person Chris    schedule 27.03.2018    source источник


Ответы (1)


Причина в том, что инвариант вашего внутреннего цикла недостаточно силен. В нем говорится только о случае, когда i == row, но ничего не говорится о том, что вы уже сделали для i < row.

Из-за того, как работают инварианты цикла, Дафни по существу забывает эту информацию, поскольку вы не помните ее в инварианте внутреннего цикла.

Вы можете исправить свою программу, усилив инвариант внутреннего цикла, чтобы говорить о i < row. Например, следующим образом:

invariant 
  forall i,j :: 
    ((0 <= i < row && 0 <= j < a.Length1) || (i == row && 0 <= j < col)) ==> 
    a[i,j] == 0

что говорит о том, что a равно нулю во всех предыдущих строках до row во всех столбцах этих строк, а также в row, но только до столбца col.


Позвольте мне также воспользоваться этой возможностью, чтобы ответить на несколько других вопросов, которые вы задали, описывая процесс отладки.

Что именно происходит, когда условие 0 <= i < row && 0 <= j < a.Length1 ложно? На первой итерации переменная row равна 0, что не удовлетворяет условию 0 <= i < row. Проходит ли он первую строку и проверяет, все ли элементы в этой строке равны нулю, или пропускает ее?

Он пропускает это. Если какая-то формула A ложна, то A ==> B верна, даже не глядя на B.

Что происходит, когда он достигает оператора row := row + 1; на начальной итерации? Использует ли инвариант цикла forall i,j :: 0 <= i < row && 0 <= j < a.Length1 ==> a[i,j] == 0 значение row при входе (которое равно нулю) или использует увеличенное значение row (1)?

Инвариант цикла должен быть истинным в начале каждого цикла. Когда вы впервые входите в цикл (еще до оценки условия перехода), оно должно быть истинным. Также после каждого выполнения тела оно должно быть верным.

Другими словами, он использует увеличенное значение row (которое равно 1 после первого выполнения тела цикла).


Наконец, позвольте мне также прокомментировать инварианты циклов в более общем плане и способы их отладки.

Во-первых, позвольте мне представить Первый закон проверки Дафни Джеймса (который я только что придумал):

Just because Dafny reports a verification error doesn't mean the program is wrong.

Сообщения об ошибках Дафни (в основном) напоминают вам об этом Законе, используя такие фразы, как может не выполняться, как в

Этот инвариант цикла может не поддерживаться циклом.

Действительно, ваша программа-пример действительно была правильной, что вы и продемонстрировали, запустив ее. При запуске он напечатал все нули, как и ожидалось. Тем не менее Дафни все же сообщил об ошибке.

Это цена, которую мы платим за решение таких неразрешимых проблем, как проверка программы. При использовании Dafny ваша задача состоит в том, чтобы убедить его в правильности вашей программы. Когда Дафни сообщает об ошибке, это просто указывает на то, что Дафни еще не убеждена.

Теперь вернемся к инвариантам цикла. Почему Дафни не убежден? Дафни проверил три вещи об инвариантах циклов.

  1. Предварительное условие перед циклом подразумевает инвариант цикла (т. е. инвариант цикла сохраняется при входе).
  2. Инвариант цикла сохраняется телом.
  3. Инвариант цикла подразумевает постусловие после цикла.

Сложный, как правило, номер 2. Дафни проверяет #2 каким-то странным и неинтуитивным способом, если вы не привыкли к программной проверке. Дафни пытается доказать, что из произвольного состояния, удовлетворяющего инварианту цикла, так что условие ветвления цикла истинно, если затем выполнить одну итерацию цикла, то инвариант цикла останется в силе.

Ключ здесь произвольный. Он не проверяет, что каждая итерация цикла, начинающаяся в состоянии, которое действительно может произойти во время выполнения программы, сохраняет инвариант цикла. Нет. Он проверяет, что выполнение, начинающееся с произвольных состояний, сохраняет инвариант цикла.

Возвращаясь к вашему примеру программы, у вас есть дважды вложенный цикл. Дафни говорит, что не был уверен, что один из инвариантов внешнего цикла был сохранен при выполнении тела внешнего цикла. Что такое тело внешнего цикла? Это выполнение множества итераций внутреннего цикла? Как Дафни рассуждает о внутренней петле? Только через его инварианты цикла.

Таким образом, при доказательстве № 2 о внешнем цикле постусловие для внутреннего цикла состоит в том, что ему необходимо восстановить инвариант внешнего цикла, который, заметьте, что-то говорит обо всех строках до row. Затем Дафни пытается доказать это, предполагая только инвариант внутреннего цикла (и отрицание условия ветвления цикла), в котором говорится только о строке row. Поскольку в остальном состояние ничем не ограничено, мы можем понять, почему Дафни не убежден.

person James Wilcox    schedule 27.03.2018