如何将这些附加限制添加到我的零售 product/shopping Z3 规范中?

How do I add these additional constraints to my retail product/shopping Z3 spec?

所以我创建了一个基本的 Z3 规格来指导我购买一些特定的零售产品。到目前为止,它所能做的就是在不超过我可用资金的情况下任意告诉我买什么:

(declare-datatypes () ((Retailer FooMart BazTrading)))
(declare-datatypes () ((Cartridge .223Rem .7.62x39mm)))
(declare-datatypes () ((Casing Brass Steel)))
(declare-datatypes () ((Offer (Offer
  (getRetailer      Retailer)
  (getCartridge     Cartridge)
  (getRounds        Int)       ; # of rounds
  (getPrice         Int)       ; price in cents
  (getCasing        Casing)
  (getQuantityAvail Int)
))))

(declare-const x1 Offer)
(declare-const x2 Offer)
(declare-const x3 Offer)
(declare-const x4 Offer)
(declare-const x5 Offer)

(assert (= x1 (Offer FooMart    .223Rem    1000 17000 Steel 50)))
(assert (= x2 (Offer BazTrading .223Rem    500  13000 Brass 10)))
(assert (= x3 (Offer FooMart    .7.62x39mm 1000 18000 Steel 15)))
(assert (= x4 (Offer BazTrading .7.62x39mm 100  1850  Steel 20)))
(assert (= x5 (Offer BazTrading .7.62x39mm 20   190   Steel 20)))

; the quantity purchased of each offer/product will be
; between 0 and the max quantity of the offer

(declare-const x1Qty Int)
(assert (>= x1Qty 0))
(assert (<= x1Qty (getQuantityAvail x1)))

(declare-const x2Qty Int)
(assert (>= x2Qty 0))
(assert (<= x2Qty (getQuantityAvail x2)))

(declare-const x3Qty Int)
(assert (>= x3Qty 0))
(assert (<= x3Qty (getQuantityAvail x3)))

(declare-const x4Qty Int)
(assert (>= x4Qty 0))
(assert (<= x4Qty (getQuantityAvail x4)))

(declare-const x5Qty Int)
(assert (>= x5Qty 0))
(assert (<= x5Qty (getQuantityAvail x5)))

; let's say i've got 0 to spend
(declare-const moneyToSpend Int)
(assert (= moneyToSpend 50000))

(declare-const amountSpent Int)
(assert (= amountSpent
  (+ (* x1Qty (getPrice x1))
     (* x2Qty (getPrice x2))
     (* x3Qty (getPrice x3))
     (* x4Qty (getPrice x4))
     (* x5Qty (getPrice x5)))
))
(assert (< amountSpent moneyToSpend))

(maximize amountSpent)
(check-sat)
(get-model)

https://rise4fun.com/Z3/WxQNw

此时我想做的是在我只想购买 Brass 盒装墨盒时添加额外的断言,或者如果我只需要特定类型的墨盒,如 .223Rem ,或为每个 Retailer 添加运费计算加上带有 minimize 约束的 $/round 计算,这样我就不会在运费上浪费金钱,或考虑到某些优惠的批量定价折扣(例如购买 5 个或更多,每件节省 0.10 美元,购买 10 件或更多,每件节省 0.15 美元。

我遇到的问题是,我觉得我为这些问题提出的解决方案是重复的,而且不是很优雅,我觉得我遗漏了什么……特别是因为我好像带着一个很多开销我自己跟踪每个 xQty 和它对应的 Offer 之间的关系。

例如,仅过滤 Brass 大小写的解决方案可能是这样的:

; non-Brass casings get set to qty 0
(assert (if (= (getCasing x1) Brass)
            (>= x1Qty 0)
            (=  x1Qty 0)))

(assert (if (= (getCasing x2) Brass)
            (>= x2Qty 0)
            (=  x2Qty 0)))

; ... etc.

也许更好的例子是尝试实施批量折扣。假设我想获取报价 x1:

的批量折扣
; at this point i'm hardcoding prices in a bag-on-the-side
; function instead of in the x1 Offer itself :/
(define-fun x1Pricing ((qty Int)) Int
  (if (>= 10 qty)
      (* qty 16500)
      (if (>= 5 qty)
          (* qty 16800)
          (* qty (getPrice x1))))
)

(declare-const amountSpent Int)
(assert (= amountSpent
  (+ (x1Pricing x1Qty)
     (* x2Qty (getPrice x2))
     (* x3Qty (getPrice x3))
     (* x4Qty (getPrice x4))
     (* x5Qty (getPrice x5)))
))
(assert (< amountSpent moneyToSpend))

感觉事情非常不同,我想知道是否需要。比如有没有办法让 x1Pricing 成为 Offer 数据类型的一部分,如果是这样,那是不是一个好方法?

我的直觉也是将事物放入列表中并在它们之上进行映射等等;但是当我看到 z3 有一个本地 List 实现时,我无法理解如何使用它或者它是否合适(使用 select 和特定索引号编写 asserts 似乎与完全单独的常量)。

所以我的问题是,是否有更优化的方式来完成我想做的事情?

从一些 Z3Py 示例中我看到我似乎可以通过使用 Python 列表理解和基于索引关联事物来减少一些重复,但我也想知道我是否错过了大局这里是我要去的方向。谢谢。

编辑:我添加了一个非常dumb/naive的运费计算方法和每轮成本计算:

(declare-const shippingCost Int)
(assert (= shippingCost
  ; FooMart charges a flat  shipping fee
  (+ (if (or (= (getRetailer x1) FooMart)
             (= (getRetailer x2) FooMart)
             (= (getRetailer x3) FooMart)
             (= (getRetailer x4) FooMart)
             (= (getRetailer x5) FooMart))
         7500
         0
     )
  ; BazTrading charges a flat  +  per 100 rounds
     (if (or (= (getRetailer x1) BazTrading)
             (= (getRetailer x2) BazTrading)
             (= (getRetailer x3) BazTrading)
             (= (getRetailer x4) BazTrading)
             (= (getRetailer x5) BazTrading))
         2000
         0
     )
     (* (/ (+ (if (= (getRetailer x1) BazTrading)
                  (getRounds x1)
                  0)
              (if (= (getRetailer x2) BazTrading)
                  (getRounds x2)
                  0)
              (if (= (getRetailer x3) BazTrading)
                  (getRounds x3)
                  0)
              (if (= (getRetailer x4) BazTrading)
                  (getRounds x4)
                  0)
              (if (= (getRetailer x5) BazTrading)
                  (getRounds x5)
                  0))
            100)
        500)
     )
  ))

(declare-const amountSpent Int)
(assert (= amountSpent
  (+ (x1Pricing x1Qty)
     (* x2Qty (getPrice x2))
     (* x3Qty (getPrice x3))
     (* x4Qty (getPrice x4))
     (* x5Qty (getPrice x5))
     shippingCost)))
(assert (< amountSpent moneyToSpend))

(declare-const totalRounds Int)
(assert (= totalRounds
  (+ (* x1Qty (getRounds x1))
     (* x2Qty (getRounds x2))
     (* x3Qty (getRounds x3))
     (* x4Qty (getRounds x4))
     (* x5Qty (getRounds x5)))
))

(declare-const amountSpentPerRound Real)
(assert (= amountSpentPerRound
  (div amountSpent totalRounds)
))

(assert (> amountSpent 20000))
(assert (< amountSpent 30000))
(minimize amountSpentPerRound)

https://rise4fun.com/Z3/vEpa3

(请注意,该模型在 rise4fun 在线编辑器中无法满足,但在我使用最新 Z3 源的本地机器上运行良好)

现在我真的认为我做错了什么,因为 shippingCost 总是 12600。当我 fiddle 和 amountSpent 最大和最小值断言试图将它推入仅使用 BazTrading 或 FooMart Offers(以降低运费),它会在我杀死它之前挂起几分钟。

SMTLib 不适合编写此类规范。它应该更多地被视为一种 "assembly" 语言,并且应该从其他工具生成。这就是高级 API 发挥作用的地方,具体取决于您选择的语言。有来自 C、C++、Java、Python、Haskell、Scala 的高级绑定。您基本上应该使用那些 APIs 来生成约束并使用 z3通过 API。每个绑定都有自己的 advantages/disadvantages,因此我建议从您最熟悉的宿主语言开始并使用它。