Maximilian Wittmann
unread,Jun 26, 2023, 11:37:32 PM6/26/23Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to sage-devel
Hey everyone,
I am currently doing my masters thesis on the dimension of cartesian products of posets and believe I have stumbled upon an alternative approach to calculate the dimension, that performs better than the one in sage. (No published results)
C = posets.Crown(2)
C2 = C.product(C)
%time order_dimension(C2, solver="kissat")
CPU times: user 96.1 ms, sys: 1e+03 ns, total: 96.1 ms
Wall time: 302 ms
%time C2.dimension(solver="gurobi")
CPU times: user 2.33 s, sys: 28 ms, total: 2.36 s
Wall time: 2.19 s
b = posets.BooleanLattice(5)
%time order_dimension(b, solver="kissat")
CPU times: user 1.13 s, sys: 12 ms, total: 1.14 s
Wall time: 1.46 s
#gurobi did not finish, even after waiting a couple of minutes
The idea is to reduce order dimension to SAT in the following way:
Let P be an arbitrary poset.
A linear extension of P corresponds to some orientation of the incomparable pairs of P, such that transitivity constraints are satisfied. So introduce a new variable x_(a,b) for each incomparable pair (a, b), where the order of a and b has been arbitrarliy fixed.
We want x_p = true iff a < b in the linear extension and x_p = false iff b < a. Now for each
(a, c) incomparable and each b introduce a new clause (not x_(a,b) v not x_(b,c) v x_(a,b)), which directly corresponds to the transitivity constraints. Of course (a,b) or (b,c) may be comparable already. In this case we can just set x_(a,b) to true or false and correspondingly eliminate the literal or clause.
This formulation allows us to find one linear extension. Now to find a realizer of size k, take k independent copies of above CNF-Formula and add additional constraints to make sure that each incomparable pair appears in both orientations at least once. I.e. clauses
(x_(a,b),1 v x_(a,b),1 v ... v x_(a,b),k) and (not x_(a,b),1 v not x_(a,b),2 v ... v not x_(a,b), k) for each incomparable pair (a,b) where x_(a,b),i denotes the i-th copy of that variable.
This formula is satisfiable iff P has a realizer of size k.
Here is a potential implementation of this:
def order_dimension(self: FinitePoset, solver=None, certificate=False):
if self.cardinality() == 0:
return (0, []) if certificate else 0
if self.is_chain():
return (1, [self.list()]) if certificate else 1
# current bound on the chromatic number of the hypergraph
k = 2
# if a realizer is not needed, we can optimize a little
if not certificate:
# polynomial time check for dimension 2
from sage.graphs.comparability import greedy_is_comparability as is_comparability
if is_comparability(self._hasse_diagram.transitive_closure().to_undirected().complement()):
return 2
k = 3
# known upper bound for dimension
max_value = max(self.cardinality() // 2, self.width())
p = Poset(self._hasse_diagram)
elements = p.list()
#identify incomparable pairs with variables
inc_graph = p.incomparability_graph()
inc_pairs = inc_graph.edges(sort=True, labels=False)
n_inc = len(inc_pairs)
to_variables = dict((pair, i) for (i, pair) in enumerate(inc_pairs, start=1))
def get_var(a, b):
#gets the corresponding variable for one ordered incomparable pair
val = to_variables.get((a, b), None)
if not val is None:
return val
val_flip = to_variables.get((b, a), None)
if not val_flip is None:
return -val_flip
#return None if comparable
return None
# generate the clauses to form a linear extension
clauses = []
for ((a,c), ac_var) in to_variables.items():
for b in elements:
new_clause = []
ab_var = get_var(a, b)
if not ab_var is None:
new_clause.append(-ab_var)
elif (not p.is_less_than(a, b)):
continue
bc_var = get_var(b, c)
if not bc_var is None:
new_clause.append(-bc_var)
elif (not p.is_less_than(b, c)):
continue
new_clause.append(ac_var)
clauses.append(new_clause)
from sage.sat.solvers.satsolver import SAT
def build_sat(k):
sat = SAT(solver=solver)
#add clauses to find k linear extensions
for i in range(k):
modifier = i * n_inc
for clause in clauses:
new_clause = [var + sign(var)*modifier for var in clause]
sat.add_clause(new_clause)
#clauses to ensure that each incomparable pair appears in both orders
for var in range(1, n_inc+1):
sat.add_clause([var + i*n_inc for i in range(k)])
sat.add_clause([-var - i*n_inc for i in range(k)])
return sat
while True:
#attempt to find realizer for each k if the upper bound is not reached
if not certificate and k == max_value:
return k
sat = build_sat(k)
result = sat()
if result is not False:
break
k += 1
#return the dimension
if not certificate:
return k
#construct the realizer from the sat solution
diagram = p.hasse_diagram()
realizer = []
for i in range(k):
extension = diagram.copy()
for var in range(1, n_inc+1):
(a,b) = inc_pairs[var-1]
if result[var + i*n_inc]:
extension.add_edge(a, b)
else:
extension.add_edge(b, a)
realizer.append(extension.topological_sort())
return (k, [[self._list[i] for i in l] for l in realizer])
I'm not familiar with the contribution process yet, but I'll open a github issue and see if I can figure things out if you think this is worth it. Any Feedback is welcome!