Дафное заявление о существовании вызывает медлительность?

apowAddition проверяет, когда я комментирую containsInverses(g) в моем определении допустимой алгебры теории групп, но когда я раскомментирую, проверка не выполняется, смерть по тайм-ауту. Я могу предположить, что Дафни пытается создать элементы, которые соответствуют определению. Однако, поскольку я не использую это свойство в доказательстве и не обеспечиваю его выполнение леммой, почему оно вызывает проблемы в доказательстве?

datatype Group<!A> = Group(elements: set<A>, identity: A, compose: (A,A) -> A)

predicate ValidGroup<A>(g: Group<A>) {
    g.identity in g.elements &&
    isIdentity(g) &&
    closedComposition(g) &&
    associativeComposition(g) //&& 
    // containsInverses(g)
}
function apow<A>(g: Group, elem: A, n: nat): A 
    requires elem in g.elements
{
    if n == 0 then g.identity else g.compose(elem, apow(g, elem, n-1))
}


lemma {:verify true} apowAddition<A>(g: Group<A>, elem: A, n: nat, k: nat)
    requires elem in g.elements
    requires ValidGroup(g)
    ensures g.compose(apow(g, elem, n), apow(g, elem, k)) == apow(g, elem, n+k)
{
    apowClosed(g, elem, n);
    apowClosed(g, elem, k);
    if k == 0 {
        assert apow(g, elem, k) == g.identity;
        assert g.compose(apow(g, elem, n), g.identity) == apow(g, elem, n+0);
    }else if n == 0 {
        assert apow(g, elem, n) == g.identity;
        assert g.compose(g.identity, apow(g, elem, k)) == apow(g, elem, k+0);
    }else{
        apowClosed(g, elem, n-1);
        apowClosed(g, elem, n+k);
        calc {
            g.compose(apow(g, elem, n), apow(g, elem, k));
            g.compose(g.compose(elem, apow(g, elem, n-1)), apow(g, elem, k));
            g.compose(elem, g.compose(apow(g, elem, n-1), apow(g, elem, k)));
            == {apowAddition(g,elem, n-1,k);}
            g.compose(elem, apow(g, elem, n-1+k));
            apow(g, elem, n+k);
        }
    }
}
predicate isIdentity<A>(g: Group<A>) {
    forall a :: a in g.elements ==> g.compose(a,g.identity) == a && g.compose(g.identity, a) == a
}

predicate closedComposition<A>(g: Group<A>) {
    forall x,y :: x in g.elements && y in g.elements ==> g.compose(x,y) in g.elements
}

predicate associativeComposition<A>(g: Group<A>) {
    forall a,b,c :: a in g.elements && b in g.elements && c in g.elements ==> g.compose(g.compose(a,b),c) == g.compose(a, g.compose(b,c))
}

predicate containsInverses<A>(g: Group<A>) {
    forall x :: x in g.elements ==> exists xbar :: xbar in g.elements && g.compose(x,xbar) == g.identity
}

lemma apowClosed<A>(g: Group, elem: A, n: nat)
    requires elem in g.elements
    requires g.identity in g.elements
    requires isIdentity(g)
    requires closedComposition(g)
    ensures apow(g,elem, n) in g.elements
{}

Редактировать 2: добавление правильной инверсии к isInverse быстро проверяет groupCompositionInverse, но apowAddition снова бесконечно проверяет. Изменить 3: добавление apowClosed(g, elem, n+k) в apowAddition все исправляет!

predicate isInverse<A>(g: Group<A>) {
  forall x {:trigger g.inverse(x)} :: x in g.elements ==> g.compose(x,g.inverse(x)) == g.identity && g.compose(g.inverse(x),x) == g.identity
}
lemma {:verify true} groupCompositionInverse<A>(g: Group<A>, a: A, b: A, abar: A, bbar: A, abbar: A)
    requires ValidGroup(g)
    requires a in g.elements
    requires b in g.elements
    // requires containsInverses(g)
    // requires abar in g.elements
    // requires bbar in g.elements
    // requires abbar in g.elements
    requires g.inverse(a) == abar
    requires g.inverse(b) == bbar
    requires g.inverse(g.compose(a,b)) == abbar
    // requires g.compose(a, abar) == g.identity
    // requires g.compose(b, bbar) == g.identity
    // requires g.compose(g.compose(a, b), abbar) == g.identity
    ensures abbar == g.compose(bbar, abar)
{
    calc {
        g.compose(g.compose(a, b), g.compose(bbar, abar));
        ==
        g.compose(a, g.compose(g.compose(b, bbar),abar));
        ==
        g.compose(a, g.compose(g.identity,abar));
        ==
        g.compose(a, abar);
        ==
        g.identity;
    }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Триггер работает так: всякий раз, когда есть выражение, которое синтаксически соответствует шаблону в триггере, Дафни создает копию тела forall для этого выражения.

Рассматриваемая аксиома:

forall x :: 
  x in g.elements ==> 
  exists xbar :: 
    xbar in g.elements && 
    g.compose(x,xbar) == g.identity

Если вы наведете указатель мыши на поле, вы увидите, какие триггеры использует Дафни. Для форала триггером является:

x in g.elements

Это проблема, потому что тело forall генерирует новое выражение, которое также соответствует внешнему триггеру (xbar in g.elements). Это означает, что всякий раз, когда Dafny создает экземпляр внешнего триггера, он создает новое выражение, которое никогда раньше не встречалось и которое также соответствует внешнему триггеру, что вызовет еще одно создание экземпляра, которое сгенерирует еще одно новое выражение, вызовет еще одно создание экземпляра и так далее до бесконечности. .

Есть несколько способов исправить это. Можно было бы искусственно зажать спусковой крючок на внешнем форалле, чтобы его можно было активировать только вручную, например:

predicate PleaseInstantiateMe<A>(x: A) {
    true
}

predicate containsInverses<A>(g: Group<A>) {
  forall x {:trigger PleaseInstantiateMe(x)} :: 
    PleaseInstantiateMe(x) && x in g.elements ==> 
    exists xbar :: 
      xbar in g.elements && g.compose(x,xbar) == g.identity
}

Мы вводим фиктивную функцию PleaseInstantiateMe, единственная цель которой — быть шаблоном, который мы можем сказать Дафни ждать, пока он не создаст экземпляр триггера. В этой версии раскомментирование containsInverses в ValidGroup приводит к мгновенному доказательству apowAddition. Недостатком является то, что всякий раз, когда вы хотите использовать containsInverses, вам придется вручную говорить

assert PleaseInstantiateMe(whatever);

где whatever — групповой элемент, который, как вы хотите, чтобы Дафни реализовал, имеет обратный. Это может быть утомительно.

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

predicate closedInverse<A>(g: Group<A>) {
  forall x {:trigger g.inverse(x)} :: x in g.elements ==> g.inverse(x) in g.elements
}

predicate isInverse<A>(g: Group<A>) {
  forall x {:trigger g.inverse(x)} :: x in g.elements ==> g.compose(x,g.inverse(x)) == g.identity
}

predicate ValidGroup<A>(g: Group<A>) {
    g.identity in g.elements &&
    isIdentity(g) &&
    closedComposition(g) &&
    associativeComposition(g) &&
    closedInverse(g) &&
    isInverse(g)
}

Я попробовал этот подход, и он все еще казался тайм-аутом. Похоже, у вас также есть некоторые проблемы с триггерами с другими вашими аксиомами. Дафни предполагает чрезвычайно агрессивные триггеры, такие как x in g.elements, которые, скорее всего, вызовут бесконечный цикл сопоставления. Дайте мне знать, если вам нужна дополнительная помощь в реализации этого подхода.

Спасибо за ваш ответ, я чувствую, что собираюсь понять концепцию. Это сложно, потому что кажется, что требуется добавить что-то, что не является строго необходимым. Из ваших примеров я думаю, что закрытая композиция - это другая аксиома плохого поведения. Как бы я изменил их, чтобы мы могли доказывать полезные леммы о группах, не вызывая бесконечную рекурсию?

Hath995 16.01.2023 06:54

Хорошо, глядя на документ Дафни, на который вы ссылаетесь, я думаю, что он хорошо резюмирует его: «Проверка Дафни может иногда зависеть от логически нерелевантных влияний для достижения успеха, особенно когда задействованы квантификаторы». Я считаю, что это должно быть выделено жирным шрифтом и выделено.

Hath995 16.01.2023 06:55

Я до сих пор довольно часто сталкиваюсь с этой проблемой. Существуют ли другие способы отладки рекурсивных триггеров, кроме простого угадывания и проверки?

Hath995 24.01.2023 13:28

Другие вопросы по теме