Assigning items to groups with features

后端 未结 1 619
耶瑟儿~
耶瑟儿~ 2021-01-24 05:34

I have a problem where I am to assign variables to sets. Each set has a limit of variables that can be assigned to it and each variable can be assigned to some subset of the tot

相关标签:
1条回答
  • 2021-01-24 06:25

    As with any programming task, there could be many ways to solve this problem. I think the following would be the most idiomatic way of doing so in z3py. Note the use of the internal Set type, which is modeled internally by arrays. I'm choosing integers as the elements of the sets, though you can make this an enumerated type (or some other base type) if you like:

    from z3 import *
    
    s = Solver()
    
    a, b, c, d = Ints('a b c d')
    allElems = [a, b, c, d]
    s.add(Distinct(allElems))
    
    # We have 2 sets
    A, B = Consts ('A B', SetSort(IntSort()))
    allSets = [A, B]
    
    # Generic requirement: Every element belongs to some set:
    for e in allElems:
        belongs = False;
        for x in allSets:
            belongs = Or(belongs, IsMember(e, x))
        s.add(belongs)
    
    # Capacity requirements
    sizeA, sizeB = Ints('sizeA sizeB')
    s.add(SetHasSize(A, sizeA))
    s.add(SetHasSize(B, sizeB))
    s.add(sizeA <= 2)
    s.add(sizeB <= 2)
    
    # Problem specific requirements:
    s.add(Or(IsMember(a, A), IsMember(a, B)))
    s.add(IsMember(b, B))
    s.add(Or(IsMember(c, A), IsMember(c, B)))
    s.add(IsMember(d, A))
    
    # c must be in a set that's after a's set
    s.add(Implies(IsMember(a, A), IsMember(c, B)))
    s.add(Not(IsMember(a, B))) # otherwise there wouldn't be a place to put c!
    
    r = s.check()
    if r == sat:
        print(s.model())
    else:
        print("Solver said: " + r)
    

    Note how the cardinality/capacity requirements are stated using sizeA, sizeB variables. You can generalize and write your helper functions to automate most of this stuff.

    Your original problem definition was rather ambiguous, but I hope the above gives you an idea on how to proceed. In particular, we can easily express the requirement that c belongs to a set "after" a since we have only two sets around:

    s.add(Implies(IsMember(a, A), IsMember(c, B)))
    s.add(Not(IsMember(a, B))) # otherwise there wouldn't be a place to put c!
    

    but if you have more than two sets you might want to write a helper function that walks over the sets (much like I did in the "Generic requirement" part) to automate this as well. (Essentially, you'd say if A is in a specific set, then c is in one of the "later" sets. When you come to the last set, you'd need to say a is not in it, as otherwise there would be no place to put c in.)

    When I run the above program, it prints:

    [A = Lambda(k!0, Or(k!0 == 1, k!0 == 4)),
     b = 5,
     a = 1,
     d = 4,
     sizeB = 2,
     c = 3,
     sizeA = 2,
     B = Lambda(k!0, Or(k!0 == 3, k!0 == 5)),
     Ext = [else -> 5]]
    

    This can be a bit hard to read, but you'll get used to it in no time! Important parts are:

    a = 1
    b = 5
    c = 3
    d = 4
    

    Above should be self-explanatory. Since we wanted to represent elements with integers, z3 picked these ones. (Note we said Distinct to make sure they weren't the same.) You can use an enum-sort here if you want.

    The next part is the representation of the sets A and B:

    A = Lambda(k!0, Or(k!0 == 1, k!0 == 4)),
    B = Lambda(k!0, Or(k!0 == 3, k!0 == 5)),
    

    What this is saying is that A contains the elements 1 and 4 (i.e., a and d), while B contains the elements 3 and 5 (i.e., b and c). You can mostly ignore the Lambda part and the funny looking k!0 symbol, and read it as follows: Any value that is either 1 OR 4 belongs to A. And similarly for B.

    The sizeA and sizeB variables should be self-explanatory.

    You can ignore the Ext value. It's used for internal purposes by z3.

    Hope this shows you how you can structure even more complex constraints in a declarative way using built-in support for Sets.

    0 讨论(0)
提交回复
热议问题