| 1 | r""" |
| 2 | PQ-Trees |
| 3 | |
| 4 | This module implements PQ-Trees and methods to help recognise Interval Graphs. |
| 5 | |
| 6 | Class and Methods |
| 7 | ----------------- |
| 8 | |
| 9 | """ |
| 10 | |
| 11 | # Constants, to make the code more readable |
| 12 | |
| 13 | FULL = 2 |
| 14 | PARTIAL = 1 |
| 15 | EMPTY = 0 |
| 16 | ALIGNED = True |
| 17 | UNALIGNED = False |
| 18 | |
| 19 | ########################################################################## |
| 20 | # Some Lambda Functions # |
| 21 | # # |
| 22 | # As the elements of a PQ-Tree can be either P-Trees, Q-Trees, or the # |
| 23 | # sets themselves (the leaves), the following lambda function are # |
| 24 | # meant to be applied both on PQ-Trees and Sets, and mimic for the # |
| 25 | # latter the behaviour we expect from the corresponding methods # |
| 26 | # defined in class PQ # |
| 27 | ########################################################################## |
| 28 | |
| 29 | set_contiguous = lambda tree, x : ( |
| 30 | tree.set_contiguous(x) if isinstance(tree, PQ) else |
| 31 | ((FULL, ALIGNED) if x in tree |
| 32 | else (EMPTY, ALIGNED))) |
| 33 | |
| 34 | new_P = lambda liste : P(liste) if len(liste) > 1 else liste[0] |
| 35 | new_Q = lambda liste : Q(liste) if len(liste) > 1 else liste[0] |
| 36 | |
| 37 | flatten = lambda x : x.flatten() if isinstance(x, PQ) else x |
| 38 | |
| 39 | impossible_msg = "Impossible" |
| 40 | |
| 41 | def reorder_sets(sets): |
| 42 | r""" |
| 43 | Reorders a collection of sets such that each element appears on an |
| 44 | interval. |
| 45 | |
| 46 | Given a collection of sets `C = S_1,...,S_k` on a ground set `X`, |
| 47 | this function attempts to reorder them in such a way that `\forall |
| 48 | x \in X` and `i<j` with `x\in S_i, S_j`, then `x\in S_l` for every |
| 49 | `i<l<j` if it exists. |
| 50 | |
| 51 | INPUT: |
| 52 | |
| 53 | - ``sets`` - a list of instances of ``list, Set`` or ``set`` |
| 54 | |
| 55 | ALGORITHM: |
| 56 | |
| 57 | PQ-Trees |
| 58 | |
| 59 | EXAMPLE: |
| 60 | |
| 61 | There is only one way (up to reversal) to represent contiguously |
| 62 | the sequence ofsets `\{i-1, i, i+1\}`:: |
| 63 | |
| 64 | sage: from sage.graphs.pq_trees import reorder_sets |
| 65 | sage: seq = [Set([i-1,i,i+1]) for i in range(1,15)] |
| 66 | |
| 67 | We apply a random permutation:: |
| 68 | |
| 69 | sage: p = Permutations(len(seq)).random_element() |
| 70 | sage: seq = [ seq[p(i+1)-1] for i in range(len(seq)) ] |
| 71 | sage: ordered = reorder_sets(seq) |
| 72 | sage: if not 0 in ordered[0]: |
| 73 | ... ordered = ordered.reverse() |
| 74 | sage: print ordered |
| 75 | [{0, 1, 2}, {1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}, {5, 6, 7}, {8, 6, 7}, {8, 9, 7}, {8, 9, 10}, {9, 10, 11}, {10, 11, 12}, {11, 12, 13}, {12, 13, 14}, {13, 14, 15}] |
| 76 | """ |
| 77 | |
| 78 | if len(sets) == 1: |
| 79 | return sets |
| 80 | |
| 81 | s = set([]) |
| 82 | |
| 83 | for ss in sets: |
| 84 | for i in ss: |
| 85 | s.add(i) |
| 86 | |
| 87 | tree = P(sets) |
| 88 | |
| 89 | |
| 90 | for i in s: |
| 91 | tree.set_contiguous(i) |
| 92 | tree = flatten(tree) |
| 93 | |
| 94 | return tree.ordering() |
| 95 | |
| 96 | class PQ: |
| 97 | r""" |
| 98 | This class implements the PQ-Tree, used for the recognition of |
| 99 | Interval Graphs, or equivalently for matrices having the so-caled |
| 100 | "consecutive ones property". |
| 101 | |
| 102 | Briefly, we are given a collection `C=S_1, ..., S_n` of sets on a |
| 103 | common ground set `X`, and we would like to reorder the elements |
| 104 | of `C` in such a way that for every element `x\in X` such that |
| 105 | `x\in S_i` and `x\in S_j`, `i<j`, we have `x\in S_l` for all |
| 106 | `i<l<j`. This property could also be rephrased as : the sets |
| 107 | containing `x` are an interval. |
| 108 | |
| 109 | To achieve it, we will actually compute ALL the orderings |
| 110 | satisfying such constraints using the structure of PQ-Tree, by |
| 111 | adding the constraints one at a time. |
| 112 | |
| 113 | * At first, there is no constraint : all the permutations are |
| 114 | allowed. We will then build a tree composed of one node |
| 115 | linked to all the sets in our collection (his children). As |
| 116 | we want to remember that all the permutations of his |
| 117 | children are allowed, we will label it with "P", making it a |
| 118 | P-Tree. |
| 119 | |
| 120 | * We are now picking an element `x \in X`, and we want to |
| 121 | ensure that all the elements `C_x` containing it are |
| 122 | contiguous. We can remove them from their tree `T_1`, create |
| 123 | a second tree `T_2` whose only children are the `C_x`, and |
| 124 | attach this `T_2` to `T_1`. We also make this new tree a |
| 125 | `P-Tree`, as all the elements of `C_x` can be permuted as |
| 126 | long as they stay close to each other. Obviously, the whole |
| 127 | tree `T_2` can be prmuter with the other children of `T_1` |
| 128 | in any way -- it does not impair the fact that the sequence |
| 129 | of the children will ensure the sets containing `x` are |
| 130 | contiguous. |
| 131 | |
| 132 | * We would like to repeat the same procedure for `x' \in X`, |
| 133 | but we are now encountering a problem : there may be sets |
| 134 | containing both `x'` and `x`, along with others containing |
| 135 | only `x` or only `x'`. We can permute the sets containing |
| 136 | only `x'` together, or the sets containing both `x` and `x'` |
| 137 | together, but we may NOT permute all the sets containing |
| 138 | `x'` together as this may break the relationship between the |
| 139 | sets containing `x`. We need `Q`-Trees. A `Q`-Tree is a tree |
| 140 | whose children are ordered, even though their order could be |
| 141 | reversed (if the children of a `Q`-Tree are `c_1c_2 |
| 142 | ... c_k`, we can change it to `c_k ... c_2c_1`). We can now |
| 143 | express all the orderings satisfying our two constraints the |
| 144 | following way : |
| 145 | |
| 146 | * We create a tree `T_1` gathering all the elements not |
| 147 | containing `x` nor `x'`, and make it a `P-Tree` |
| 148 | |
| 149 | * We create 3 `P`-Trees `T_{x, x'}, T_x, T_{x'}`, which |
| 150 | respectively have for children the elements or our |
| 151 | collection containing |
| 152 | |
| 153 | * both `x` and `x'` |
| 154 | * only `x` |
| 155 | * only `x'` |
| 156 | |
| 157 | * To ensure our constraints on both elements, we create a |
| 158 | `Q`-tree `T_2` whose children are in order `T_x, T_{x, |
| 159 | x'}, T_{x'}` |
| 160 | |
| 161 | * We now make this `Q`-Tree `T_2` a children of the |
| 162 | `P`-Tree `T_1` |
| 163 | |
| 164 | Using these two types of tree, and exploring the different cases |
| 165 | of intersection, it is possible to represent all the possible |
| 166 | permutations of our sets satisfying or constraints, or to prove |
| 167 | that no such ordering exists. This is the whole purpose of this |
| 168 | class and this algorithm, and is explained with more details in many |
| 169 | places, for example in the following document from Hajiaghayi [Haj]_. |
| 170 | |
| 171 | REFERENCES: |
| 172 | |
| 173 | .. [Haj] M. Hajiaghayi |
| 174 | http://www-math.mit.edu/~hajiagha/pp11.ps |
| 175 | |
| 176 | AUTHOR : Nathann Cohen |
| 177 | """ |
| 178 | |
| 179 | def __init__(self, seq): |
| 180 | r""" |
| 181 | Construction of a PQ-Tree |
| 182 | |
| 183 | EXAMPLE:: |
| 184 | |
| 185 | sage: from sage.graphs.pq_trees import P, Q |
| 186 | sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])]) |
| 187 | """ |
| 188 | from sage.sets.set import Set |
| 189 | |
| 190 | self._children = [] |
| 191 | for e in seq: |
| 192 | if isinstance(e, list): |
| 193 | e = Set(e) |
| 194 | |
| 195 | if not e in self._children: |
| 196 | self._children.append(e) |
| 197 | |
| 198 | def reverse(self): |
| 199 | r""" |
| 200 | Recursively reverses ``self`` and its children |
| 201 | |
| 202 | EXAMPLE:: |
| 203 | |
| 204 | sage: from sage.graphs.pq_trees import P, Q |
| 205 | sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])]) |
| 206 | sage: p.ordering() |
| 207 | [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}] |
| 208 | sage: p.reverse() |
| 209 | sage: p.ordering() |
| 210 | [{9, 2}, {8, 2}, {2, 4}, {2, 3}, {1, 2}] |
| 211 | """ |
| 212 | for i in self._children: |
| 213 | if isinstance(i, PQ): |
| 214 | i.reverse() |
| 215 | |
| 216 | self._children.reverse() |
| 217 | |
| 218 | def __contains__(self, v): |
| 219 | r""" |
| 220 | Tests whether there exists an element of ``self`` containing |
| 221 | an element ``v`` |
| 222 | |
| 223 | INPUT: |
| 224 | |
| 225 | - ``v`` -- an element of the ground set |
| 226 | |
| 227 | EXAMPLE:: |
| 228 | |
| 229 | sage: from sage.graphs.pq_trees import P, Q |
| 230 | sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])]) |
| 231 | sage: 5 in p |
| 232 | False |
| 233 | sage: 9 in p |
| 234 | True |
| 235 | |
| 236 | """ |
| 237 | for i in self: |
| 238 | if v in i: |
| 239 | return True |
| 240 | False |
| 241 | |
| 242 | def split(self, v): |
| 243 | r""" |
| 244 | Returns the subsequences of children containing and not |
| 245 | containing ``v`` |
| 246 | |
| 247 | INPUT: |
| 248 | |
| 249 | - ``v`` -- an element of the ground set |
| 250 | |
| 251 | OUTPUT: |
| 252 | |
| 253 | Two lists, the first containing the children of ``self`` |
| 254 | containing ``v``, and the other containing the other children. |
| 255 | |
| 256 | .. NOTE:: |
| 257 | |
| 258 | This command is meant to be used on a partial tree, once it |
| 259 | has be "set continuous" on an element ``v`` and aligned it |
| 260 | to the right. Hence, none of the list should be empty (an |
| 261 | exception is raised if that happens, as it would reveal a |
| 262 | bug in the algorithm) and the sum ``contains + |
| 263 | does_not_contain`` should be equal to the sequence of |
| 264 | children of ``self``. |
| 265 | |
| 266 | EXAMPLE:: |
| 267 | |
| 268 | sage: from sage.graphs.pq_trees import P, Q |
| 269 | sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])]) |
| 270 | sage: p.reverse() |
| 271 | sage: contains, does_not_contain = p.split(1) |
| 272 | sage: contains |
| 273 | [{1, 2}] |
| 274 | sage: does_not_contain |
| 275 | [('P', [{9, 2}, {8, 2}, {2, 4}]), {2, 3}] |
| 276 | sage: does_not_contain + contains == p._children |
| 277 | True |
| 278 | |
| 279 | """ |
| 280 | contains = [] |
| 281 | does_not_contain = [] |
| 282 | |
| 283 | for i in self: |
| 284 | if v in i: |
| 285 | contains.append(i) |
| 286 | else: |
| 287 | does_not_contain.append(i) |
| 288 | |
| 289 | if not contains or not does_not_contain: |
| 290 | raise ValueError("None of the sets should be empty !") |
| 291 | |
| 292 | return contains, does_not_contain |
| 293 | |
| 294 | def __iter__(self): |
| 295 | r""" |
| 296 | Iterates over the children of ``self``. |
| 297 | |
| 298 | EXAMPLE:: |
| 299 | |
| 300 | sage: from sage.graphs.pq_trees import P, Q |
| 301 | sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])]) |
| 302 | sage: for i in p: |
| 303 | ... print i |
| 304 | {1, 2} |
| 305 | {2, 3} |
| 306 | ('P', [{2, 4}, {8, 2}, {9, 2}]) |
| 307 | """ |
| 308 | |
| 309 | for i in self._children: |
| 310 | yield i |
| 311 | |
| 312 | def cardinality(self): |
| 313 | r""" |
| 314 | Returns the number of children of ``self`` |
| 315 | |
| 316 | EXAMPLE:: |
| 317 | |
| 318 | sage: from sage.graphs.pq_trees import P, Q |
| 319 | sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])]) |
| 320 | sage: p.cardinality() |
| 321 | 3 |
| 322 | """ |
| 323 | return len(self._children) |
| 324 | |
| 325 | def ordering(self): |
| 326 | r""" |
| 327 | Returns the current ordering given by listing the leaves from |
| 328 | left to right. |
| 329 | |
| 330 | EXAMPLE: |
| 331 | |
| 332 | sage: from sage.graphs.pq_trees import P, Q |
| 333 | sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])]) |
| 334 | sage: p.ordering() |
| 335 | [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}] |
| 336 | """ |
| 337 | value = [] |
| 338 | for i in self: |
| 339 | if isinstance(i, PQ): |
| 340 | value.extend(i.ordering()) |
| 341 | else: |
| 342 | value.append(i) |
| 343 | |
| 344 | return value |
| 345 | |
| 346 | def __repr__(self): |
| 347 | r""" |
| 348 | Succintly represents ``self``. |
| 349 | |
| 350 | EXAMPLE:: |
| 351 | |
| 352 | sage: from sage.graphs.pq_trees import P, Q |
| 353 | sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])]) |
| 354 | sage: print p |
| 355 | ('Q', [{1, 2}, {2, 3}, ('P', [{2, 4}, {8, 2}, {9, 2}])]) |
| 356 | """ |
| 357 | return str((("P" if self.is_P() else "Q"),self._children)) |
| 358 | |
| 359 | def simplify(self, v, left = False, right = False): |
| 360 | r""" |
| 361 | Returns a simplified copy of self according to the element ``v`` |
| 362 | |
| 363 | If ``self`` is a partial P-tree for ``v``, we would like to |
| 364 | restrict the permutations of its children to permutations |
| 365 | keeping the children containing ``v`` contiguous. This |
| 366 | function also "locks" all the elements not containing ``v`` |
| 367 | inside a `P`-tree, which is useful when one want to keep the |
| 368 | elements containing ``v`` on one side (which is the case when |
| 369 | this method is called). |
| 370 | |
| 371 | INPUT: |
| 372 | |
| 373 | - ``left, right`` (booleans) -- whether ``v`` is aligned to the |
| 374 | right or to the left |
| 375 | |
| 376 | - ``v```-- an element of the ground set |
| 377 | |
| 378 | OUTPUT: |
| 379 | |
| 380 | If ``self`` is a `Q`-Tree, the sequence of its children is |
| 381 | returned. If ``self`` is a `P`-tree, 2 `P`-tree are returned, |
| 382 | namely the two `P`-tree defined above and restricting the |
| 383 | permutations, in the order implied by ``left, right`` (if |
| 384 | ``right =True``, the second `P`-tree will be the one gathering |
| 385 | the elements containing ``v``, if ``left=True``, the |
| 386 | opposite). |
| 387 | |
| 388 | .. NOTE:: |
| 389 | |
| 390 | This method is assumes that ``self`` is partial for ``v``, |
| 391 | and aligned to the side indicated by ``left, right``. |
| 392 | |
| 393 | EXAMPLES: |
| 394 | |
| 395 | A `P`-Tree :: |
| 396 | |
| 397 | sage: from sage.graphs.pq_trees import P, Q |
| 398 | sage: p = P([[2,4], [1,2], [0,8], [0,5]]) |
| 399 | sage: p.simplify(0, right = True) |
| 400 | [('P', [{2, 4}, {1, 2}]), ('P', [{0, 8}, {0, 5}])] |
| 401 | |
| 402 | A `Q`-Tree :: |
| 403 | |
| 404 | sage: q = Q([[2,4], [1,2], [0,8], [0,5]]) |
| 405 | sage: q.simplify(0, right = True) |
| 406 | [{2, 4}, {1, 2}, {0, 8}, {0, 5}] |
| 407 | """ |
| 408 | if sum([left, right]) !=1: |
| 409 | raise ValueError("Exactly one of left or right must be specified") |
| 410 | |
| 411 | if self.is_Q(): |
| 412 | return self._children |
| 413 | else: |
| 414 | |
| 415 | contains, does_not_contain = self.split(v) |
| 416 | |
| 417 | A = new_P(does_not_contain) |
| 418 | B = new_P(contains) |
| 419 | |
| 420 | if right: |
| 421 | return [A, B] |
| 422 | else: |
| 423 | return [B, A] |
| 424 | |
| 425 | def flatten(self): |
| 426 | r""" |
| 427 | Returns a flattened copy of ``self`` |
| 428 | |
| 429 | If self has only one child, we may as well consider its |
| 430 | child's children, as ``self`` encodes no information. This |
| 431 | method recursively "flattens" trees having only on PQ-tree |
| 432 | child, and returns it. |
| 433 | |
| 434 | EXAMPLE:: |
| 435 | |
| 436 | sage: from sage.graphs.pq_trees import P, Q |
| 437 | sage: p = Q([P([[2,4], [2,8], [2,9]])]) |
| 438 | sage: p.flatten() |
| 439 | ('P', [{2, 4}, {8, 2}, {9, 2}]) |
| 440 | """ |
| 441 | if self.cardinality() == 1: |
| 442 | return flatten(self._children[0]) |
| 443 | else: |
| 444 | self._children = [flatten(x) for x in self._children] |
| 445 | return self |
| 446 | |
| 447 | |
| 448 | def is_P(self): |
| 449 | r""" |
| 450 | Tests whether ``self`` is a `P`-Tree |
| 451 | |
| 452 | EXAMPLE:: |
| 453 | |
| 454 | sage: from sage.graphs.pq_trees import P, Q |
| 455 | sage: P([[0,1],[2,3]]).is_P() |
| 456 | True |
| 457 | sage: Q([[0,1],[2,3]]).is_P() |
| 458 | False |
| 459 | """ |
| 460 | return isinstance(self,P) |
| 461 | |
| 462 | def is_Q(self): |
| 463 | r""" |
| 464 | Tests whether ``self`` is a `Q`-Tree |
| 465 | |
| 466 | EXAMPLE:: |
| 467 | |
| 468 | sage: from sage.graphs.pq_trees import P, Q |
| 469 | sage: Q([[0,1],[2,3]]).is_Q() |
| 470 | True |
| 471 | sage: P([[0,1],[2,3]]).is_Q() |
| 472 | False |
| 473 | """ |
| 474 | return isinstance(self,Q) |
| 475 | |
| 476 | class P(PQ): |
| 477 | r""" |
| 478 | A P-Tree is a PQ-Tree whose children are |
| 479 | not ordered (they can be permuted in any way) |
| 480 | """ |
| 481 | def set_contiguous(self, v): |
| 482 | r""" |
| 483 | Updates ``self`` so that its sets containing ``v`` are |
| 484 | contiguous for any admissible permutation of its subtrees. |
| 485 | |
| 486 | This function also ensures, whenever possible, |
| 487 | that all the sets containing ``v`` are located on an interval |
| 488 | on the right side of the ordering. |
| 489 | |
| 490 | INPUT: |
| 491 | |
| 492 | - ``v`` -- an element of the ground set |
| 493 | |
| 494 | OUTPUT: |
| 495 | |
| 496 | According to the cases : |
| 497 | |
| 498 | * ``(EMPTY, ALIGNED)`` if no set of the tree contains |
| 499 | an occurrence of ``v`` |
| 500 | |
| 501 | * ``(FULL, ALIGNED)`` if all the sets of the tree contain |
| 502 | ``v`` |
| 503 | |
| 504 | * ``(PARTIAL, ALIGNED)`` if some (but not all) of the sets |
| 505 | contain ``v``, all of which are aligned |
| 506 | to the right of the ordering at the end when the function ends |
| 507 | |
| 508 | * ``(PARTIAL, UNALIGNED)`` if some (but not all) of the |
| 509 | sets contain ``v``, though it is impossible to align them |
| 510 | all to the right |
| 511 | |
| 512 | In any case, the sets containing ``v`` are contiguous when this |
| 513 | function ends. If there is no possibility of doing so, the function |
| 514 | raises a ``ValueError`` exception. |
| 515 | |
| 516 | EXAMPLE: |
| 517 | |
| 518 | Ensuring the sets containing ``0`` are continuous:: |
| 519 | |
| 520 | sage: from sage.graphs.pq_trees import P, Q |
| 521 | sage: p = P([[0,3], [1,2], [2,3], [2,4], [4,0],[2,8], [2,9]]) |
| 522 | sage: p.set_contiguous(0) |
| 523 | (1, True) |
| 524 | sage: print p |
| 525 | ('P', [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}, ('P', [{0, 3}, {0, 4}])]) |
| 526 | |
| 527 | Impossible situation:: |
| 528 | |
| 529 | sage: p = P([[0,1], [1,2], [2,3], [3,0]]) |
| 530 | sage: p.set_contiguous(0) |
| 531 | (1, True) |
| 532 | sage: p.set_contiguous(1) |
| 533 | (1, True) |
| 534 | sage: p.set_contiguous(2) |
| 535 | (1, True) |
| 536 | sage: p.set_contiguous(3) |
| 537 | Traceback (most recent call last): |
| 538 | ... |
| 539 | ValueError: Impossible |
| 540 | |
| 541 | """ |
| 542 | |
| 543 | ############################################################### |
| 544 | # Defining Variables : # |
| 545 | # # |
| 546 | # Collecting the information of which children are FULL of v, # |
| 547 | # which ones are EMPTY, PARTIAL_ALIGNED and PARTIAL_UNALIGNED # |
| 548 | # # |
| 549 | # Defining variables for their cardinals, just to make the # |
| 550 | # code slightly more readable :-) # |
| 551 | ############################################################### |
| 552 | |
| 553 | seq = [set_contiguous(x, v) for x in self] |
| 554 | self.flatten() |
| 555 | seq = [set_contiguous(x, v) for x in self] |
| 556 | |
| 557 | f_seq = dict(zip(self, seq)) |
| 558 | |
| 559 | set_FULL = [] |
| 560 | set_EMPTY = [] |
| 561 | set_PARTIAL_ALIGNED = [] |
| 562 | set_PARTIAL_UNALIGNED = [] |
| 563 | |
| 564 | sorting = { |
| 565 | (FULL, ALIGNED) : set_FULL, |
| 566 | (EMPTY, ALIGNED) : set_EMPTY, |
| 567 | (PARTIAL, ALIGNED) : set_PARTIAL_ALIGNED, |
| 568 | (PARTIAL, UNALIGNED) : set_PARTIAL_UNALIGNED |
| 569 | } |
| 570 | |
| 571 | for i in self: |
| 572 | sorting[f_seq[i]].append(i) |
| 573 | |
| 574 | n_FULL = len(set_FULL) |
| 575 | n_EMPTY = len(set_EMPTY) |
| 576 | n_PARTIAL_ALIGNED = len(set_PARTIAL_ALIGNED) |
| 577 | n_PARTIAL_UNALIGNED = len(set_PARTIAL_UNALIGNED) |
| 578 | |
| 579 | counts = dict(map(lambda (x,y) : (x,len(y)), sorting.iteritems())) |
| 580 | |
| 581 | # Excludes the situation where there is no solution. |
| 582 | # read next comment for more explanations |
| 583 | |
| 584 | if (n_PARTIAL_ALIGNED + n_PARTIAL_UNALIGNED > 2 or |
| 585 | (n_PARTIAL_UNALIGNED >= 1 and n_EMPTY != self.cardinality() -1)): |
| 586 | |
| 587 | raise ValueError(impossible_msg) |
| 588 | |
| 589 | # From now on, there are at most two pq-trees which are partially filled |
| 590 | # If there is one which is not aligned to the right, all the others are empty |
| 591 | |
| 592 | ######################################################### |
| 593 | # 1/2 # |
| 594 | # # |
| 595 | # Several easy cases where we can decide without paying # |
| 596 | # attention # |
| 597 | ######################################################### |
| 598 | |
| 599 | # All the children are FULL |
| 600 | elif n_FULL == self.cardinality(): |
| 601 | return FULL, True |
| 602 | |
| 603 | # All the children are empty |
| 604 | elif n_EMPTY == self.cardinality(): |
| 605 | return EMPTY, True |
| 606 | |
| 607 | # There is a PARTIAL UNALIGNED element (and all the others are |
| 608 | # empty as we checked before |
| 609 | |
| 610 | elif n_PARTIAL_UNALIGNED == 1: |
| 611 | return (PARTIAL, UNALIGNED) |
| 612 | |
| 613 | # If there is just one partial element and all the others are |
| 614 | # empty, we just reorder the set to put it at the right end |
| 615 | |
| 616 | elif (n_PARTIAL_ALIGNED == 1 and |
| 617 | n_EMPTY == self.cardinality()-1): |
| 618 | |
| 619 | self._children = set_EMPTY + set_PARTIAL_ALIGNED |
| 620 | return (PARTIAL, ALIGNED) |
| 621 | |
| 622 | |
| 623 | ################################################################ |
| 624 | # 2/2 # |
| 625 | # # |
| 626 | # From now on, there are at most two partial pq-trees and all # |
| 627 | # of them have v aligned to their right # |
| 628 | # # |
| 629 | # We now want to order them in such a way that all the # |
| 630 | # elements containing v are located on the right # |
| 631 | ################################################################ |
| 632 | |
| 633 | else: |
| 634 | |
| 635 | self._children = [] |
| 636 | |
| 637 | # We first move the empty elements to the left, if any |
| 638 | |
| 639 | if n_EMPTY > 0: |
| 640 | self._children.extend(set_EMPTY) |
| 641 | |
| 642 | # If there is one partial element we but have to add it to |
| 643 | # the sequence, then add all the full elements |
| 644 | |
| 645 | # We must also make sure these elements will not be |
| 646 | # reordered in such a way that the elements containing v |
| 647 | # are not contiguous |
| 648 | |
| 649 | # ==> We create a Q-tree |
| 650 | |
| 651 | if n_PARTIAL_ALIGNED < 2: |
| 652 | |
| 653 | new = [] |
| 654 | |
| 655 | |
| 656 | # add the partial element, if any |
| 657 | if n_PARTIAL_ALIGNED == 1: |
| 658 | |
| 659 | subtree = set_PARTIAL_ALIGNED[0] |
| 660 | new.extend(subtree.simplify(v, right = ALIGNED)) |
| 661 | |
| 662 | |
| 663 | # Then the full elements, if any, in a P-tree (we can |
| 664 | # permute any two of them while keeping all the |
| 665 | # elements containing v on an interval |
| 666 | |
| 667 | if n_FULL > 0: |
| 668 | |
| 669 | new.append(new_P(set_FULL)) |
| 670 | |
| 671 | # We lock all of them in a Q-tree |
| 672 | |
| 673 | self._children.append(new_Q(new)) |
| 674 | |
| 675 | return PARTIAL, True |
| 676 | |
| 677 | # If there are 2 partial elements, we take care of both |
| 678 | # ends. We also know it will not be possible to align the |
| 679 | # interval of sets containing v to the right |
| 680 | |
| 681 | else: |
| 682 | |
| 683 | new = [] |
| 684 | |
| 685 | # The second partal element is aligned to the right |
| 686 | # while, as we want to put it at the end of the |
| 687 | # interval, it should be aligned to the left |
| 688 | set_PARTIAL_ALIGNED[1].reverse() |
| 689 | |
| 690 | # 1/3 |
| 691 | # Left partial subtree |
| 692 | subtree = set_PARTIAL_ALIGNED[0] |
| 693 | new.extend(subtree.simplify(v, right = ALIGNED)) |
| 694 | |
| 695 | # 2/3 |
| 696 | # Center (Full elements, in a P-tree, as they can be |
| 697 | # permuted) |
| 698 | |
| 699 | if n_FULL > 0: |
| 700 | new.append(new_P(set_FULL)) |
| 701 | |
| 702 | # 3/3 |
| 703 | # Right partial subtree |
| 704 | subtree = set_PARTIAL_ALIGNED[1] |
| 705 | new.extend(subtree.simplify(v, left= ALIGNED)) |
| 706 | |
| 707 | # We add all of it, locked in a Q-Tree |
| 708 | self._children.append(new_Q(new)) |
| 709 | |
| 710 | return PARTIAL, False |
| 711 | |
| 712 | class Q(PQ): |
| 713 | r""" |
| 714 | A Q-Tree is a PQ-Tree whose children are |
| 715 | ordered up to reversal |
| 716 | """ |
| 717 | |
| 718 | def set_contiguous(self, v): |
| 719 | r""" |
| 720 | Updates ``self`` so that its sets containing ``v`` are |
| 721 | contiguous for any admissible permutation of its subtrees. |
| 722 | |
| 723 | This function also ensures, whenever possible, |
| 724 | that all the sets containing ``v`` are located on an interval |
| 725 | on the right side of the ordering. |
| 726 | |
| 727 | INPUT: |
| 728 | |
| 729 | - ``v`` -- an element of the ground set |
| 730 | |
| 731 | OUTPUT: |
| 732 | |
| 733 | According to the cases : |
| 734 | |
| 735 | * ``(EMPTY, ALIGNED)`` if no set of the tree contains |
| 736 | an occurrence of ``v`` |
| 737 | |
| 738 | * ``(FULL, ALIGNED)`` if all the sets of the tree contain |
| 739 | ``v`` |
| 740 | |
| 741 | * ``(PARTIAL, ALIGNED)`` if some (but not all) of the sets |
| 742 | contain ``v``, all of which are aligned |
| 743 | to the right of the ordering at the end when the function ends |
| 744 | |
| 745 | * ``(PARTIAL, UNALIGNED)`` if some (but not all) of the |
| 746 | sets contain ``v``, though it is impossible to align them |
| 747 | all to the right |
| 748 | |
| 749 | In any case, the sets containing ``v`` are contiguous when this |
| 750 | function ends. If there is no possibility of doing so, the function |
| 751 | raises a ``ValueError`` exception. |
| 752 | |
| 753 | EXAMPLE: |
| 754 | |
| 755 | Ensuring the sets containing ``0`` are continuous:: |
| 756 | |
| 757 | sage: from sage.graphs.pq_trees import P, Q |
| 758 | sage: q = Q([[2,3], Q([[3,0],[3,1]]), Q([[4,0],[4,5]])]) |
| 759 | sage: q.set_contiguous(0) |
| 760 | (1, False) |
| 761 | sage: print q |
| 762 | ('Q', [{2, 3}, {1, 3}, {0, 3}, {0, 4}, {4, 5}]) |
| 763 | |
| 764 | Impossible situation:: |
| 765 | |
| 766 | sage: p = Q([[0,1], [1,2], [2,0]]) |
| 767 | sage: p.set_contiguous(0) |
| 768 | Traceback (most recent call last): |
| 769 | ... |
| 770 | ValueError: Impossible |
| 771 | |
| 772 | |
| 773 | """ |
| 774 | |
| 775 | |
| 776 | ################################################################# |
| 777 | # Guidelines : # |
| 778 | # # |
| 779 | # As the tree is a Q-Tree, we can but reverse the order in # |
| 780 | # which the elements appear. It means that we can but check # |
| 781 | # the elements containing v are already contiguous (even # |
| 782 | # though we have to take special care of partial elements -- # |
| 783 | # the endpoints of the interval), and answer accordingly # |
| 784 | # (partial, full, empty, aligned..). We also want to align the # |
| 785 | # elements containing v to the right if possible. # |
| 786 | ################################################################ |
| 787 | |
| 788 | |
| 789 | ############################################################### |
| 790 | # Defining Variables : # |
| 791 | # # |
| 792 | # Collecting the information of which children are FULL of v, # |
| 793 | # which ones are EMPTY, PARTIAL_ALIGNED and PARTIAL_UNALIGNED # |
| 794 | # # |
| 795 | # Defining variables for their cardinals, just to make the # |
| 796 | # code slightly more readable :-) # |
| 797 | ############################################################### |
| 798 | |
| 799 | seq = [set_contiguous(x, v) for x in self] |
| 800 | self.flatten() |
| 801 | seq = [set_contiguous(x, v) for x in self] |
| 802 | |
| 803 | f_seq = dict(zip(self, seq)) |
| 804 | |
| 805 | set_FULL = [] |
| 806 | set_EMPTY = [] |
| 807 | set_PARTIAL_ALIGNED = [] |
| 808 | set_PARTIAL_UNALIGNED = [] |
| 809 | |
| 810 | sorting = { |
| 811 | (FULL, ALIGNED) : set_FULL, |
| 812 | (EMPTY, ALIGNED) : set_EMPTY, |
| 813 | (PARTIAL, ALIGNED) : set_PARTIAL_ALIGNED, |
| 814 | (PARTIAL, UNALIGNED) : set_PARTIAL_UNALIGNED |
| 815 | } |
| 816 | |
| 817 | for i in self: |
| 818 | sorting[f_seq[i]].append(i) |
| 819 | |
| 820 | n_FULL = len(set_FULL) |
| 821 | n_EMPTY = len(set_EMPTY) |
| 822 | n_PARTIAL_ALIGNED = len(set_PARTIAL_ALIGNED) |
| 823 | n_PARTIAL_UNALIGNED = len(set_PARTIAL_UNALIGNED) |
| 824 | |
| 825 | counts = dict(map(lambda (x,y) : (x,len(y)), sorting.iteritems())) |
| 826 | |
| 827 | ################################################################### |
| 828 | # # |
| 829 | # Picking the good ordering for the children : # |
| 830 | # # |
| 831 | # # |
| 832 | # There is a possibility of aligning to the right iif # |
| 833 | # the vector can assume the form (as a regular expression) : # |
| 834 | # # |
| 835 | # (EMPTY *) PARTIAL (FULL *) Of course, each of these three # |
| 836 | # members could be empty # |
| 837 | # # |
| 838 | # Hence, in the following case we reverse the vector : # |
| 839 | # # |
| 840 | # * if the last element is empty (as we checked the whole # |
| 841 | # vector is not empty # |
| 842 | # # |
| 843 | # * if the last element is partial, aligned, and all the # |
| 844 | # others are full # |
| 845 | ################################################################### |
| 846 | |
| 847 | if (f_seq[self._children[-1]] == (EMPTY, ALIGNED) or |
| 848 | (f_seq[self._children[-1]] == (PARTIAL, ALIGNED) and n_FULL == self.cardinality() - 1)): |
| 849 | |
| 850 | # We reverse the order of the elements in the SET only. Which means that they are still aligned to the right ! |
| 851 | self._children.reverse() |
| 852 | |
| 853 | |
| 854 | ######################################################### |
| 855 | # 1/2 # |
| 856 | # # |
| 857 | # Several easy cases where we can decide without paying # |
| 858 | # attention # |
| 859 | ######################################################### |
| 860 | |
| 861 | |
| 862 | # Excludes the situation where there is no solution. |
| 863 | # read next comment for more explanations |
| 864 | |
| 865 | if (n_PARTIAL_ALIGNED + n_PARTIAL_UNALIGNED > 2 or |
| 866 | (n_PARTIAL_UNALIGNED >= 1 and n_EMPTY != self.cardinality() -1)): |
| 867 | |
| 868 | raise ValueError(impossible_msg) |
| 869 | |
| 870 | # From now on, there are at most two pq-trees which are partially filled |
| 871 | # If there is one which is not aligned to the right, all the others are empty |
| 872 | |
| 873 | # First trivial case, no checking neded |
| 874 | elif n_FULL == self.cardinality(): |
| 875 | return FULL, True |
| 876 | |
| 877 | # Second trivial case, no checking needed |
| 878 | elif n_EMPTY == self.cardinality(): |
| 879 | return EMPTY, True |
| 880 | |
| 881 | # Third trivial case, no checking needed |
| 882 | elif n_PARTIAL_UNALIGNED == 1: |
| 883 | return (PARTIAL, UNALIGNED) |
| 884 | |
| 885 | # If there is just one partial element |
| 886 | # and all the others are empty, we just reorder |
| 887 | # the set to put it at the right end |
| 888 | |
| 889 | elif (n_PARTIAL_ALIGNED == 1 and |
| 890 | n_EMPTY == self.cardinality()-1): |
| 891 | |
| 892 | if set_PARTIAL_ALIGNED[0] == self._children[-1]: |
| 893 | return (PARTIAL, ALIGNED) |
| 894 | |
| 895 | else: |
| 896 | return (PARTIAL, UNALIGNED) |
| 897 | |
| 898 | |
| 899 | ############################################################## |
| 900 | # 2/2 # |
| 901 | # # |
| 902 | # We iteratively consider all the children, and check # |
| 903 | # that the elements containing v are indeed # |
| 904 | # locate on an interval. # |
| 905 | # # |
| 906 | # We are also interested in knowing whether this interval is # |
| 907 | # aligned to the right # |
| 908 | # # |
| 909 | # Because of the previous tests, we can assume there are at # |
| 910 | # most two partial pq-trees and all of them are aligned to # |
| 911 | # their right # |
| 912 | ############################################################## |
| 913 | |
| 914 | else: |
| 915 | |
| 916 | new_children = [] |
| 917 | |
| 918 | # Two variables to remember where we are |
| 919 | # according to the interval |
| 920 | |
| 921 | seen_nonempty = False |
| 922 | seen_right_end = False |
| 923 | |
| 924 | |
| 925 | for i in self: |
| 926 | |
| 927 | type, aligned = f_seq[i] |
| 928 | |
| 929 | # We met an empty element |
| 930 | if type == EMPTY: |
| 931 | |
| 932 | # 2 possibilities : |
| 933 | # |
| 934 | # * we have NOT met a non-empty element before |
| 935 | # and it just means we are looking at the |
| 936 | # leading empty elements |
| 937 | # |
| 938 | # * we have met a non-empty element before and it |
| 939 | # means we will never met another non-empty |
| 940 | # element again => we have seen the right end |
| 941 | # of the interval |
| 942 | |
| 943 | new_children.append(i) |
| 944 | |
| 945 | if seen_nonempty: |
| 946 | seen_right_end = True |
| 947 | |
| 948 | # We met a non-empty element |
| 949 | else: |
| 950 | if seen_right_end: |
| 951 | raise ValueError(impossible_msg) |
| 952 | |
| 953 | |
| 954 | if type == PARTIAL: |
| 955 | |
| 956 | # if we see an ALIGNED partial tree after |
| 957 | # having seen a nonempty element then the |
| 958 | # partial tree must be aligned to the left and |
| 959 | # so we have seen the right end |
| 960 | |
| 961 | if seen_nonempty and aligned: |
| 962 | i.reverse() |
| 963 | seen_right_end = True |
| 964 | |
| 965 | # right partial subtree |
| 966 | subtree = i |
| 967 | new_children.extend(subtree.simplify(v, left = True)) |
| 968 | |
| 969 | # If we see an UNALIGNED partial element after |
| 970 | # having met a nonempty element, there is no |
| 971 | # solution to the alignment problem |
| 972 | |
| 973 | elif seen_nonempty and not aligned: |
| 974 | raise ValueError(impossible_msg) |
| 975 | |
| 976 | # If we see an unaligned element but no non-empty |
| 977 | # element since the beginning, we are witnessing both the |
| 978 | # left and right end |
| 979 | |
| 980 | elif not seen_nonempty and not aligned: |
| 981 | raise ValueError("Bon, ben ca arrive O_o") |
| 982 | seen_right_end = True |
| 983 | |
| 984 | elif not seen_nonempty and aligned: |
| 985 | |
| 986 | # left partial subtree |
| 987 | subtree = i |
| 988 | |
| 989 | new_children.extend(subtree.simplify(v, right = True)) |
| 990 | |
| 991 | |
| 992 | else: |
| 993 | new_children.append(i) |
| 994 | |
| 995 | seen_nonempty = True |
| 996 | |
| 997 | # Setting the updated sequence of children |
| 998 | self._children = new_children |
| 999 | |
| 1000 | |
| 1001 | # Whether we achieved an alignment to the right is the |
| 1002 | # complement of whether we have seen the right end |
| 1003 | |
| 1004 | return (PARTIAL, not seen_right_end) |
| 1005 | |