z3py: 如何在使用不同构造函数时防止对自定义数据类型的访问器调用?

huangapple go评论60阅读模式
英文:

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 是一个关于全函数的逻辑。当你定义一个代数数据类型时,与该数据类型一起定义的所有函数(例如,你的示例中的 headtail)都是总函数。也就是说,它们可以应用于任何列表值,无论该值如何构建。

类型系统中没有任何东西可以禁止这样做。(要做到这一点需要依赖类型,而 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.

huangapple
  • 本文由 发表于 2023年6月12日 09:19:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76453153.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定