z3: предложения Horn / фиксированные точки над индуктивными типами данных
Я пытаюсь сделать утверждения об эквивалентности программ, используя фиксированные точки z3, и обнаружил, что любая рекурсия, даже тривиально разрешимая рекурсия, полностью подавляет z3. В этом руководстве по z3py в качестве примера использования фиксированных точек используется моделирование программ, при этом программы представлены рекурсивными типами данных. Я чувствую, что делаю что-то не так.
Вот простой пример:
from z3 import *
List = Datatype('List')
List.declare('nil')
List.declare('cons', ('car', List), ('cdr', List))
List = List.create()
nil = List.nil
cons = List.cons
a = Const('a', List)
RightLL = Function('RightLL', List, BoolSort())
fp = Fixedpoint()
fp.declare_var(a)
fp.register_relation(RightLL)
fp.fact(RightLL(nil))
fp.rule(RightLL(cons(nil, a)), RightLL(a))
BIG_ISH = cons(nil, cons(nil, cons(nil, cons(nil, cons(nil, cons(nil, cons(nil, cons(nil, cons(nil, cons(nil, nil))))))))))
fp.query(RightLL(BIG_ISH))
RightLL
является унарным отношением для деревьев, продолжающихся только вправо.
nil
распространяется только вправо, и если вы это сделаете
cons(nil, a)
в списке
a
что распространяется только вправо, оно продолжает расширяться вправо. Похоже, это должно быть тривиально.
Но на скромно глубоком дереве код вращается бесконечно. Вот статистика после перерыва на несколько минут:
(:SPACER-max-depth 7
:SPACER-max-query-lvl 7
:SPACER-num-active-lemmas 9
:SPACER-num-ctp-blocked 11
:SPACER-num-is_invariant 27
:SPACER-num-lemmas 21
:SPACER-num-pobs 6
:SPACER-num-propagations 4
:SPACER-num-queries 18
:added-eqs 103061776
:bool-inductive-gen 15
:bool-inductive-gen-failures 12
:conflicts 31312
:datatype-accessor-ax 10890974
:datatype-constructor-ax 25388634
:datatype-occurs-check 51492242
:datatype-splits 34477418
:decisions 38226758
:del-clause 656
:final-checks 23408
:iuc_solver.num_proxies 63
:max-memory 55.70
:memory 55.33
:minimized-lits 3476
:mk-bool-var 52243150
:mk-clause 31440
:num-allocs 1205059883276.00
:num-checks 660
:pool_solver.checks 330
:pool_solver.checks.sat 263
:pool_solver.checks.undef 1
:propagations 36882678
:restarts 198
:rlimit-count 277012208
:time.iuc_solver.get_iuc 0.00
:time.iuc_solver.get_iuc.hyp_reduce2 0.00
:time.pool_solver.proof 0.00
:time.pool_solver.smt.total 197.77
:time.pool_solver.smt.total.sat 193.37
:time.pool_solver.smt.total.undef 4.22
:time.spacer.init_rules 0.00
:time.spacer.mbp 0.00
:time.spacer.solve 197.88
:time.spacer.solve.propagate 10.48
:time.spacer.solve.reach 187.39
:time.spacer.solve.reach.children 0.00
:time.spacer.solve.reach.gen.bool_ind 168.29)
Честно говоря, я понятия не имею, что здесь может попробовать z3... Меньшие списки и списки, которые не соответствуют предикату (когда минус слева находится в верхней части дерева), заполняются быстро.
Я что делаю неправильно? Или z3 просто неспособен обрабатывать рекурсивно определенные предикаты над алгебраическими типами данных?
1 ответ
Не ответ, но потенциально полезный.
Я измерил, как время выполнения зависит от глубины вложенности выражения запроса:
from z3 import *
import time
List = Datatype('List')
List.declare('nil')
List.declare('cons', ('car', List), ('cdr', List))
List = List.create()
nil = List.nil
cons = List.cons
a = Const('a', List)
RightLL = Function('RightLL', List, BoolSort())
fp = Fixedpoint()
fp.declare_var(a)
fp.register_relation(RightLL)
fp.fact(RightLL(nil))
fp.rule(RightLL(cons(nil, a)), RightLL(a))
expr = nil
for nesting in range(20):
start = time.time()
result = fp.query(RightLL(expr))
end = time.time()
print(nesting, " ", result, " ", round(end - start, 2), "s", flush=True)
expr = cons(nil, expr)
Результат:
0 sat 0.0 s
1 sat 0.02 s
2 sat 0.02 s
3 sat 0.03 s
4 sat 0.23 s
5 sat 2.71 s
6 sat 8.86 s
7 sat 37.21 s
Z3 требует экспоненциально растущего времени, чтобы ответить на
Fixedpoint
запрос. Если глубина вложения превышает 7, ответ не возвращается, как уже отмечалось в вопросе.
Экспоненциальный рост не является неожиданным для рекурсивно определенной древовидной задачи.