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;
}
}
Эта аксиома проблематична для реализации квантификатора на основе триггера. Триггер — это синтаксический шаблон, включающий количественные переменные, который служит эвристикой для решателя, чтобы что-то сделать с квантификатором. При использовании квантификатора 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
, которые, скорее всего, вызовут бесконечный цикл сопоставления. Дайте мне знать, если вам нужна дополнительная помощь в реализации этого подхода.
Хорошо, глядя на документ Дафни, на который вы ссылаетесь, я думаю, что он хорошо резюмирует его: «Проверка Дафни может иногда зависеть от логически нерелевантных влияний для достижения успеха, особенно когда задействованы квантификаторы». Я считаю, что это должно быть выделено жирным шрифтом и выделено.
Я до сих пор довольно часто сталкиваюсь с этой проблемой. Существуют ли другие способы отладки рекурсивных триггеров, кроме простого угадывания и проверки?
Спасибо за ваш ответ, я чувствую, что собираюсь понять концепцию. Это сложно, потому что кажется, что требуется добавить что-то, что не является строго необходимым. Из ваших примеров я думаю, что закрытая композиция - это другая аксиома плохого поведения. Как бы я изменил их, чтобы мы могли доказывать полезные леммы о группах, не вызывая бесконечную рекурсию?