mirror of
https://github.com/larstvei/SMT-for-IN3070.git
synced 2024-11-26 11:38:31 +00:00
Move /should/ to the optimizing part, and fill that part out
This commit is contained in:
parent
b6ac1a131f
commit
312b396f3c
185
committees.org
185
committees.org
@ -254,46 +254,29 @@ cry for help.
|
|||||||
return [0 <= comms[c] for c in comms]
|
return [0 <= comms[c] for c in comms]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
** Committee constraints
|
||||||
|
|
||||||
The $S$ relation is sort of odd. That one examiner /should/ form a committee
|
The $S$ relation is sort of odd. That one examiner /should/ form a committee
|
||||||
with someone they relate to by $S$. Let's interpret it as $e_1$ should not
|
with someone they relate to by $S$. This is not an absolute requirement,
|
||||||
form a committee to someone they are not related to by $S$.
|
which is not ideal for a satisfiability problem, so we will ignore this
|
||||||
|
constraint for now. The $A$ relation is similar, but clearer. For any pair
|
||||||
#+BEGIN_SRC python :tangle committees.py
|
$(i,j) \in A$, we don't form a committee consisting of those examiners.
|
||||||
def should_correct_with_constraint(comms, S, C):
|
|
||||||
examiners = set(range(len(C)))
|
|
||||||
return [comms[frozenset([i, j])] == 0
|
|
||||||
for i in S
|
|
||||||
for j in examiners - S[i]
|
|
||||||
if j != i]
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
The $A$ relation is similar, and easier.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :tangle committees.py
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
def avoid_correct_with_constraint(comms, A):
|
def avoid_correct_with_constraint(comms, A):
|
||||||
return [comms[frozenset([i, j])] == 0 for i, j in A]
|
return [comms[frozenset([i, j])] == 0 for i, j in A]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Each committee will correct their exams to times, so if the sum of all the
|
** All exams are corrected constraint
|
||||||
committees is $N$, then all exams have been corrected twice. Let's encode
|
|
||||||
that as a constraint.
|
Each committee correct their exams two times (once by each examiner), so if
|
||||||
|
the sum of all the committees is $N$, then all exams have been corrected
|
||||||
|
twice (presumably by two different examiners). Let's encode that as a
|
||||||
|
constraint.
|
||||||
|
|
||||||
#+BEGIN_SRC python :tangle committees.py
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
def all_corrected_constraint(comms, N):
|
def all_corrected_constraint(comms, N):
|
||||||
return [sum(comms.values()) == N]
|
return sum(comms.values()) == N
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Let's collect all the constraints in a single list.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :tangle committees.py
|
|
||||||
def constraints(instance):
|
|
||||||
N, C, S, A = instance
|
|
||||||
comms = committees(C)
|
|
||||||
return (capacity_constraint(comms, C) +
|
|
||||||
non_negative_constraint(comms) +
|
|
||||||
all_corrected_constraint(comms, N) +
|
|
||||||
should_correct_with_constraint(comms, S, C) +
|
|
||||||
avoid_correct_with_constraint(comms, A))
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Invoking Z3
|
** Invoking Z3
|
||||||
@ -302,9 +285,15 @@ cry for help.
|
|||||||
|
|
||||||
#+BEGIN_SRC python :tangle committees.py
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
def check_instance(instance):
|
def check_instance(instance):
|
||||||
|
N, C, S, A = instance
|
||||||
|
comms = committees(C)
|
||||||
|
|
||||||
s = Solver()
|
s = Solver()
|
||||||
|
|
||||||
s.add(constraints(instance))
|
s.add(capacity_constraint(comms, C))
|
||||||
|
s.add(non_negative_constraint(comms))
|
||||||
|
s.add(all_corrected_constraint(comms, N))
|
||||||
|
s.add(avoid_correct_with_constraint(comms, A))
|
||||||
|
|
||||||
s.check()
|
s.check()
|
||||||
return s.model()
|
return s.model()
|
||||||
@ -333,12 +322,17 @@ cry for help.
|
|||||||
This is not especially readable, so let's write a quick prettyprinter.
|
This is not especially readable, so let's write a quick prettyprinter.
|
||||||
|
|
||||||
#+BEGIN_SRC python :tangle committees.py
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
def prettyprint(m):
|
def prettyprint(instance, m):
|
||||||
for k in m:
|
N, C, S, A = instance
|
||||||
cname = k.name()[10:-1]
|
comms = committees(C)
|
||||||
cval = m[k].as_long()
|
exams = [sum(m[comms[c]].as_long() for c in comms if i in c)
|
||||||
if cval > 0:
|
for i in range(len(C))]
|
||||||
print(cname + ':', cval)
|
examiners = '\n'.join(['%d: %d/%d' % (i, exams[i], C[i])
|
||||||
|
for i in range(len(C))])
|
||||||
|
cs = [(c, m[comms[c]].as_long()) for c in sorted(comms, key=sorted)]
|
||||||
|
csstr = '\n'.join([', '.join(map(str, sorted(c))) + ': ' + str(cv)
|
||||||
|
for c, cv in cs if cv > 0])
|
||||||
|
print(examiners + '\n\n' + csstr)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
This outputs the something like:
|
This outputs the something like:
|
||||||
@ -353,23 +347,23 @@ cry for help.
|
|||||||
#+END_EXAMPLE
|
#+END_EXAMPLE
|
||||||
|
|
||||||
Note the /something like/. There are multiple ways to satisfy this set of
|
Note the /something like/. There are multiple ways to satisfy this set of
|
||||||
constraints, and Z3 only guarantees to provide /some/ solution (if one
|
constraints, and Z3 only provide /some/ solution (if one exists).
|
||||||
exists).
|
|
||||||
|
|
||||||
* Optimization
|
* Optimization
|
||||||
|
|
||||||
So far, we have found a way to model the problem and satisfy all the
|
So far, we have found a way to model the problem and satisfy the constraints.
|
||||||
constraint. However, it is preferable to have fewer committees, because all
|
However, it is preferable to have fewer committees, because all committees
|
||||||
committees have to discuss the exams, causing administrative overhead. Z3
|
have to discuss the exams, causing administrative overhead. Z3 also provides
|
||||||
also provides optimization, meaning that we can find a smallest or largest
|
optimization, meaning that we can find a smallest or largest solution for
|
||||||
solution for numeric theories.
|
numeric theories. The underlying theory for optimization is MaxSMT.
|
||||||
|
|
||||||
** Minimize committees
|
** Minimize committees
|
||||||
|
|
||||||
In our case, we want to minimize the number of committees.
|
In our case, we want to minimize the number of committees. First we write a
|
||||||
|
function to find the number of committees which we will soon minimize.
|
||||||
|
|
||||||
#+BEGIN_SRC python :tangle committees.py
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
def minimize_committees(comms):
|
def number_of_committees(comms):
|
||||||
return sum(If(0 < comms[c], 1, 0) for c in comms)
|
return sum(If(0 < comms[c], 1, 0) for c in comms)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
@ -378,10 +372,17 @@ cry for help.
|
|||||||
|
|
||||||
#+BEGIN_SRC python :tangle committees.py
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
def optimize_instance(instance):
|
def optimize_instance(instance):
|
||||||
|
N, C, S, A = instance
|
||||||
|
comms = committees(C)
|
||||||
|
|
||||||
o = Optimize()
|
o = Optimize()
|
||||||
|
|
||||||
o.add(constraints(instance))
|
o.add(capacity_constraint(comms, C))
|
||||||
o.minimize(minimize_committees(committees(instance)))
|
o.add(non_negative_constraint(comms))
|
||||||
|
o.add(all_corrected_constraint(comms, N))
|
||||||
|
o.add(avoid_correct_with_constraint(comms, A))
|
||||||
|
|
||||||
|
o.minimize(number_of_committees(comms))
|
||||||
|
|
||||||
o.check()
|
o.check()
|
||||||
return o.model()
|
return o.model()
|
||||||
@ -400,6 +401,92 @@ cry for help.
|
|||||||
{1, 4}: 13
|
{1, 4}: 13
|
||||||
#+END_EXAMPLE
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
** Dealing with /should/
|
||||||
|
|
||||||
|
Remember $S$, which maps examiners to other examiners they /should/ form a
|
||||||
|
committee with. With optimization, we now have a way of expressing that some
|
||||||
|
solution is more preferable than another. One way to model this is
|
||||||
|
maximizing the number of exams given to committees that consists of an
|
||||||
|
examiner $i$ that should be in a committee with examiner $j$. We want this
|
||||||
|
for all such pairs $i,j$, and can achieve this by summing all such
|
||||||
|
committees.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
|
def should_correct_with_weight(comms, S, C):
|
||||||
|
return sum(comms[frozenset([i, j])] for i in S for j in S[i])
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
When adding multiple optimization objectives (or goals), Z3 defaults to
|
||||||
|
order the objectives lexicographically, i.e. in the order they appear. If we
|
||||||
|
place the minimization of committees before the
|
||||||
|
~should_correct_with_weight~, then we still are guaranteed to get a minimal
|
||||||
|
number of committees.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
|
def optimize_instance(instance):
|
||||||
|
N, C, S, A = instance
|
||||||
|
comms = committees(C)
|
||||||
|
|
||||||
|
o = Optimize()
|
||||||
|
|
||||||
|
o.add(capacity_constraint(comms, C))
|
||||||
|
o.add(non_negative_constraint(comms))
|
||||||
|
o.add(all_corrected_constraint(comms, N))
|
||||||
|
o.add(avoid_correct_with_constraint(comms, A))
|
||||||
|
|
||||||
|
o.minimize(number_of_committees(comms))
|
||||||
|
o.maximize(should_correct_with_weight(comms, S, C))
|
||||||
|
|
||||||
|
o.check()
|
||||||
|
return o.model()
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
{0, 2}: 50
|
||||||
|
{1, 3}: 60
|
||||||
|
{1, 5}: 7
|
||||||
|
{2, 4}: 60
|
||||||
|
{0, 5}: 23
|
||||||
|
{0, 1}: 83
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
** Optimize for capacities
|
||||||
|
|
||||||
|
Maybe we can try to satisfy (🙃) all the examiners by trying to close the
|
||||||
|
gap between their capacity and the number of exams they end up correcting.
|
||||||
|
Usually at the Department, there is quite a lot of flex in these capacities;
|
||||||
|
if you are willing to correct $50$ exams, then you will most likely be okey
|
||||||
|
with correcting $40$ and /actually/ willing to correct $52$. Therefore, we
|
||||||
|
can try to add some slack to the capacity.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
|
def capacity_slack(comms, i, C):
|
||||||
|
a = sum(comms[c] for c in comms if i in c)
|
||||||
|
return If(a > C[i], a - C[i], C[i] - a)
|
||||||
|
|
||||||
|
def capacity_weight(comms, C):
|
||||||
|
return sum(capacity_slack(comms, i, C) for i in range(len(C)))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC python :tangle committees.py
|
||||||
|
def optimize_instance(instance):
|
||||||
|
N, C, S, A = instance
|
||||||
|
comms = committees(C)
|
||||||
|
|
||||||
|
o = Optimize()
|
||||||
|
|
||||||
|
o.add(non_negative_constraint(comms))
|
||||||
|
o.add(all_corrected_constraint(comms, N))
|
||||||
|
o.add(avoid_correct_with_constraint(comms, A))
|
||||||
|
|
||||||
|
o.minimize(capacity_weight(comms, C))
|
||||||
|
o.minimize(number_of_committees(comms))
|
||||||
|
o.maximize(should_correct_with_weight(comms, S, C))
|
||||||
|
|
||||||
|
o.check()
|
||||||
|
return o.model()
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
** COMMENT Implementation draft
|
** COMMENT Implementation draft
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
@ -412,7 +499,7 @@ cry for help.
|
|||||||
s.add(capacity_constraint(comms, C))
|
s.add(capacity_constraint(comms, C))
|
||||||
s.add(non_negative_constraint(comms))
|
s.add(non_negative_constraint(comms))
|
||||||
s.add(all_corrected_constraint(comms, N))
|
s.add(all_corrected_constraint(comms, N))
|
||||||
s.add(should_correct_with_constraint(comms, S, C))
|
s.add(should_correct_with_weight(comms, S, C))
|
||||||
s.add(avoid_correct_with_constraint(comms, A))
|
s.add(avoid_correct_with_constraint(comms, A))
|
||||||
|
|
||||||
s.check()
|
s.check()
|
||||||
|
Loading…
Reference in New Issue
Block a user