英文:
z3py: How to prevent accessor call on custom data type when a differen constructor is used?
问题
I created a custom List-Datatype similar to the tutorial on https://ericpony.github.io/z3py-tutorial/advanced-examples.htm:
def ListSort(sort):
# Declare a List of sort elements
listType = Datatype('List(%s)' % str(sort))
listType.declare('cons', ('head', sort), ('tail', listType))
listType.declare('empty')
return listType.create()
IntListSort = ListSort(IntSort())
I now want to use this type, but when solving the following formula, I get a weird result:
solve(Int("x") == 1 + IntListSort.head(IntListSort.empty))
Output:
[x = 1 + head(empty)]
While this sounds like a perfectly reasonable result when just looking at the code, this was definitely not my intention. head(empty)
shouldn't really return an element of sort IntSort()
. head(List)
shouldn't even be available for empty lists, only for lists created using cons
.
This might not be an issue for programs where I manually craft the equations to be solved, but I need to use this in a more complex context, and my program just gave me such an "integer" when looking for a counter example, but such a result is not really helpful.
Is there a way to restrict head
such that it can only be applied to lists constructed using cons
?
One idea I thought about was to create a separate type called EmptyListSort
, but I would still need to allow ListSorts to be empty as well, so this would lead back to the same issue...
英文:
I created a custom List-Datatype similar to the tutorial on https://ericpony.github.io/z3py-tutorial/advanced-examples.htm:
def ListSort(sort):
# Declare a List of sort elements
listType = Datatype('List(%s)' % str(sort))
listType.declare('cons', ('head', sort), ('tail', listType))
listType.declare('empty')
return listType.create()
IntListSort = ListSort(IntSort())
I now want to use this type, but when solving the following formula, I get a weird result:
solve(Int("x") == 1 + IntListSort.head(IntListSort.empty))
Output
[x = 1 + head(empty)]
While this sounds like a perfectly reasonable result when just looking at the code, this was definitely not my intention. head(empty)
shouldn't really return an element of sort IntSort()
. head(List)
shouldn't even be available for empty lists, only for lists created using cons
.
This might not be an issue for programs where I manually craft the equations to be solved, but I need to use this in a more complex context, and my program just gave me such an "integer" when looking for a counter example, but such a result is not really helpful.
Is there a way to restrict head
such that it can only be applied to lists constructed using cons
?
One idea I thought about was to create a separate type called EmptyListSort
, but I would still need to allow ListSorts to be empty as well, so this would lead back to the same issue...
答案1
得分: 0
SMTLib 是一个关于全函数的逻辑。当你定义一个代数数据类型时,与该数据类型一起定义的所有函数(例如,你的示例中的 head
、tail
)都是总函数。也就是说,它们可以应用于任何列表值,无论该值如何构建。
类型系统中没有任何东西可以禁止这样做。(要做到这一点需要依赖类型,而 SMTLib 的类型系统远没有那么复杂。)
那么,如果你将 head
应用于 empty
会发生什么呢?逻辑会将其保留为未指定状态:也就是说,求解器可以为 head(empty)
的结果选择任何值。如果在不同上下文中两次使用此表达式,它甚至可以选择不同的值。
那么,如何避免你遇到的问题呢?嗯,你必须在应用 head
时添加自己的约束条件。也就是说,你必须编程以使如果在 empty
上调用 head
,则强制求解器处于 unsat
状态。为此,我会定义一个自定义版本的 head
,它断言所需的条件:
def customHead(s, lst):
s.add(IntListSort.is_cons(lst))
return IntListSort.head(lst)
这个 customHead
的定义与 head
等效,除了它强制要求接收到的所有参数都是 cons
节点。现在你可以像这样使用它:
s = Solver()
x = Int('x')
s.add(x == 1 + customHead(s, IntListSort.empty))
print(s.check())
这将打印:
unsat
也就是说,它通过强制求解器返回 unsat
来“捕获”你想要避免的不良情况。这可能适用于你的最终目标,但这是建模此类行为的唯一方式。
如果我们传递一个 cons
列表会发生什么呢?
s = Solver()
x = Int('x')
s.add(x == 1 + customHead(s, IntListSort.cons(10, IntListSort.empty)))
print(s.check())
print(s.model())
这将打印:
sat
[x = 11]
正如你所期望的那样。
英文:
SMTLib is a logic of total-functions. When you define an algebraic data-type, then all the functions that get defined along with that data-type (i.e., head
, tail
in your example) are always total. That is, they can be applied to any list value, regardless of how that value is constructed.
There's nothing in the type-system that'd allow you to prohibit this. (Doing so would require a notion of dependent types, and SMTLib's type system is nowhere near that sophisticated.)
Then, you might ask, what happens if you apply head
to empty
: The logic leaves it underspecified: That is, the solver can pick any value it wants for the result of head(empty)
. It can even pick different values if you use this expression twice in different contexts.
So, how does one avoid the problem you're having? Well, you have to add your own constraints when you apply head
. That is, you have to program such that if head
is called on empty
, you force the solver to be in an unsat
state. To do so, I'd define a custom version of head, that asserts the required condition:
def customHead(s, lst):
s.add(IntListSort.is_cons(lst))
return IntListSort.head(lst)
This definition of customHead
is equivalent to head
, except it forces all the arguments it receives to be cons
nodes. Now you can use it like this:
s = Solver()
x = Int('x')
s.add(x == 1 + customHead(s, IntListSort.empty))
print(s.check())
And this prints:
unsat
That is, it "catches" the bad case you wanted to avoid, by forcing the solver to return unsat
. This may or may not be suitable for your eventual goal; but this is the only way to model such behavior.
And here is what happens if we pass a cons
list:
s = Solver()
x = Int('x')
s.add(x == 1 + customHead(s, IntListSort.cons(10, IntListSort.empty)))
print(s.check())
print(s.model())
This prints:
sat
[x = 11]
as you'd expect.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论