Ticket #12090: trac_12090-inprogress.patch

File trac_12090-inprogress.patch, 24.6 KB (added by ncohen, 8 years ago)
  • doc/en/reference/geometry/index.rst

    # HG changeset patch
    # User Nathann Cohen <nathann.cohen@gmail.com>
    # Date 1322923750 -3600
    # Node ID 8fe3ef78303c8b33ebdda0861f3df868ebcdeb9a
    # Parent  c546bff2410cca88a1efa338b57ce6433bf0bdef
    trac #12090 - Arrangements of pseudolines
    * * *
    implement pseudo-line arrangements (review patch)
    * * *
    Arrangements of pseudolines -- pseudolines use the braid plot
    
    diff --git a/doc/en/reference/geometry/index.rst b/doc/en/reference/geometry/index.rst
    a b  
    66polytopes and polyhedra (with rational or numerical coordinates).
    77
    88.. toctree::
    9    :maxdepth: 2
     9   :maxdepth: 1
    1010
    11    sage/geometry/toric_lattice   
     11   sage/geometry/toric_lattice
    1212   sage/geometry/cone
    1313   sage/geometry/fan
    1414   sage/geometry/fan_morphism
    1515   sage/geometry/point_collection
    1616   sage/geometry/toric_plotter
    17    
     17
    1818   sage/rings/polynomial/groebner_fan
    19    
     19
    2020   sage/geometry/lattice_polytope
    2121
    2222   sage/geometry/polyhedron/constructor
     
    3131   sage/geometry/polyhedron/backend_cdd
    3232   sage/geometry/polyhedron/backend_ppl
    3333   sage/geometry/polyhedron/cdd_file_format
     34   sage/geometry/pseudolines
    3435
    3536   sage/geometry/triangulation/point_configuration
    3637   sage/geometry/triangulation/base
  • doc/en/reference/plotting/index.rst

    diff --git a/doc/en/reference/plotting/index.rst b/doc/en/reference/plotting/index.rst
    a b  
    22===========
    33
    44.. toctree::
    5    :maxdepth: 2
     5   :maxdepth: 1
    66
    77   sage/plot/plot
    88   sage/plot/graphics
  • sage/geometry/all.py

    diff --git a/sage/geometry/all.py b/sage/geometry/all.py
    a b  
    1515
    1616from toric_lattice import ToricLattice
    1717
     18import sage.geometry.pseudolines
     19
    1820
    1921import toric_plotter
  • new file sage/geometry/pseudolines.py

    diff --git a/sage/geometry/pseudolines.py b/sage/geometry/pseudolines.py
    new file mode 100644
    - +  
     1r"""
     2Pseudolines
     3
     4This module gathers everything that has to do with pseudolines, and for a start
     5a :class:`PseudolineArrangement` class that can be used to describe an
     6arrangement of pseudolines in several different ways, and to translate one
     7description into another, as well as to display *Wiring diagrams* via the
     8:meth:`show <sage.geometry.pseudolines.PseudolineArrangement.show>` method.
     9
     10In the following, we try to stick to the terminology given in [Felsner]_, which
     11can be checked in case of doubt. And please fix this module's documentation
     12afterwards :-)
     13
     14**Definition**
     15
     16A *pseudoline* can not be defined by itself, though it can be thought of as a
     17`x`-monotone curve in the plane. A *set* of pseudolines, however, represents a
     18set of such curves that pairwise intersect exactly once (and hence mimic the
     19behaviour of straight lines in general position). We also assume that those
     20pseudolines are in general position, that is that no three of them cross at the
     21same point.
     22
     23The present class is made to deal with a combinatorial encoding of a pseudolines
     24arrangement, that is the ordering in which a pseudoline `l_i` of an arrangement
     25`l_0, ..., l_{n-1}` crosses the `n-1` other lines.
     26
     27.. WARNING::
     28
     29    It is assumed through all the methods that the given lines are numbered
     30    according to their `y`-coordinate on the vertical line `x=-\infty`.
     31    For instance, it is not possible that the first transposition be ``(0,2)``
     32    (or equivalently that the first line `l_0` crosses is `l_2` and conversely),
     33    because one of them would have to cross `l_1` first.
     34
     35Encodings
     36----------
     37
     38**Permutations**
     39
     40An arrangement of pseudolines can be described by a sequence of `n` lists of
     41length `n-1`, where the `i` list is a permutation of `\{0, ..., n-1\} \backslash
     42i` representing the ordering in which the `i` th pseudoline meets the other
     43ones.
     44
     45::
     46
     47    sage: from sage.geometry.pseudolines import PseudolineArrangement
     48    sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     49    sage: p = PseudolineArrangement(permutations)
     50    sage: p
     51    Arrangement of pseudolines of size 4
     52    sage: p.show()
     53
     54**Sequence of transpositions**
     55
     56An arrangement of pseudolines can also be described as a sequence of `\binom n
     572` transpositions (permutations of two elements). In this sequence, the
     58transposition `(2,3)` appears before `(8, 2)` iif `l_2` crosses `l_3` before it
     59crosses `l_8`. This encoding is easy to obtain by reading the wiring diagram
     60from left to right (see the :meth:`show
     61<sage.geometry.pseudolines.PseudolineArrangement.show>` method).
     62
     63::
     64
     65    sage: from sage.geometry.pseudolines import PseudolineArrangement
     66    sage: transpositions = [(3, 2), (3, 1), (0, 3), (2, 1), (0, 2), (0, 1)]
     67    sage: p = PseudolineArrangement(transpositions)
     68    sage: p
     69    Arrangement of pseudolines of size 4
     70    sage: p.show()
     71
     72
     73Note that this ordering is not necessarily unique.
     74
     75**Felsner's Matrix**
     76
     77Felser gave an encoding of an arrangement of pseudolines that takes `n^2` bits
     78instead of the `n^2log(n)` bits required by the two previous encodings.
     79
     80Instead of storing the permutation ``[3, 2, 1]`` to remember that line `l_0`
     81crosses `l_3` then `l_2` then `l_1`, it is sufficient to remember the positions
     82for which each line `l_i` meets a line `l_j` with `j < i`. As `l_0` -- the first
     83of the lines -- can only meet pseudolines with higher index, we can store ``[0,
     840, 0]`` instead of ``[3, 2, 1]`` stored previously. For `l_1`'s permutation
     85``[3, 2, 0]`` we only need to remember that `l_1` first crosses 2 pseudolines of
     86higher index, and then a pseudoline with smaller index, which yields the bit
     87vector ``[0, 0, 1]``. Hence we can transform the list of permutations above into
     88a list of `n` bit vectors of length `n-1`, that is
     89
     90.. MATH::
     91    \begin{array}{ccc}
     92      3 & 2 & 1\\
     93      3 & 2 & 0\\
     94      3 & 1 & 0\\
     95      2 & 1 & 0\\
     96    \end{array}
     97    \Rightarrow
     98    \begin{array}{ccc}
     99      0 & 0 & 0\\
     100      0 & 0 & 1\\
     101      0 & 1 & 1\\
     102      1 & 1 & 1\\
     103    \end{array}
     104
     105In order to go back from Felsner's matrix to an encoding by a sequence of
     106transpositions, it is sufficient to look for occurrences of
     107`\begin{array}{c}0\\1\end{array}` in the first column of the matrix, as it
     108corresponds in the wiring diagram to a line going up while the line immediately
     109above it goes down -- those two lines cross. Each time such a pattern is found
     110it yields a new transposition, and the matrix can be updated so that this
     111pattern disappears. A more detailed description of this algorithm is given in
     112[Felsner]_.
     113
     114::
     115
     116    sage: from sage.geometry.pseudolines import PseudolineArrangement
     117    sage: felsner_matrix = [[0, 0, 0], [0, 0, 1], [0, 1, 1], [1, 1, 1]]
     118    sage: p = PseudolineArrangement(felsner_matrix)
     119    sage: p
     120    Arrangement of pseudolines of size 4
     121
     122Example
     123-------
     124
     125Let us define in the plane several lines `l_i` of equation `y = a x+b` by
     126picking a coefficient `a` and `b` for each of them. We make sure that no two of
     127them are parallel by making sure all of the `a` chosen are different, and we
     128avoid a common crossing of three lines by adding a random noise to `b`::
     129
     130    sage: n = 20
     131    sage: l = zip(Subsets(20*n,n).random_element(), [randint(0,20*n)+random() for i in range(n)])
     132    sage: print l[:5]                            # not tested
     133    [(96, 278.0130613051349), (74, 332.92512282478714), (13, 155.65820951249867), (209, 34.753946221755307), (147, 193.51376457741441)]
     134    sage: l.sort()
     135
     136We can now compute for each `i` the order in which line `i` meets the other lines::
     137
     138    sage: permutations = [[0..i-1]+[i+1..n-1] for i in range(n)]
     139    sage: a = lambda x : l[x][0]
     140    sage: b = lambda x : l[x][1]
     141    sage: for i, perm in enumerate(permutations):
     142    ....:     perm.sort(key = lambda j : (b(j)-b(i))/(a(i)-a(j)))
     143
     144And finally build the line arrangement::
     145
     146    sage: from sage.geometry.pseudolines import PseudolineArrangement
     147    sage: p = PseudolineArrangement(permutations)
     148    sage: print p
     149    Arrangement of pseudolines of size 20
     150    sage: p.show(figsize=[20,8])
     151
     152
     153References:
     154
     155.. [Felsner] On the Number of Arrangements of Pseudolines,
     156  Stefan Felsner,
     157  http://page.math.tu-berlin.de/~felsner/Paper/numarr.pdf
     158
     159Author:
     160
     161- Nathann Cohen (2012)
     162
     163Methods
     164-------
     165"""
     166##############################################################################
     167#       Copyright (C) 2011 Nathann Cohen <nathann.cohen@gmail.com>
     168#  Distributed under the terms of the GNU General Public License (GPL)
     169#  The full text of the GPL is available at:
     170#                  http://www.gnu.org/licenses/
     171##############################################################################
     172
     173from copy import deepcopy
     174
     175class PseudolineArrangement:
     176
     177    def __init__(self, seq, encoding = "auto"):
     178        r"""
     179        Creates an arrangement of pseudolines.
     180
     181        INPUT:
     182
     183        - ``seq`` (a sequence describing the line arrangement). It can be :
     184
     185            - A list of `n` permutations of size `n-1`.
     186            - A list of `\binom n 2` transpositions
     187            - A Felsner matrix, given as a sequence of `n` binary vectors of
     188              length `n-1`.
     189
     190        - ``encoding`` (information on how the data should be interpreted), and
     191          can assume any value among 'transpositions', 'permutations', 'Felsner'
     192          or 'auto'. In the latter case, the type will be guessed (default
     193          behaviour).
     194
     195        .. NOTE::
     196
     197           * The pseudolines are assumed to be integers `0..(n-1)`.
     198
     199           * For more information on the different encodings, see the
     200             :mod:`pseudolines module <sage.geometry.pseudolines>`'s
     201             documentation.
     202
     203        TESTS:
     204
     205        From permutations::
     206
     207            sage: from sage.geometry.pseudolines import PseudolineArrangement
     208            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     209            sage: PseudolineArrangement(permutations)
     210            Arrangement of pseudolines of size 4
     211
     212        From transpositions ::
     213
     214            sage: from sage.geometry.pseudolines import PseudolineArrangement
     215            sage: transpositions = [(3, 2), (3, 1), (0, 3), (2, 1), (0, 2), (0, 1)]
     216            sage: PseudolineArrangement(transpositions)
     217            Arrangement of pseudolines of size 4
     218
     219        From a Felsner matrix::
     220
     221            sage: from sage.geometry.pseudolines import PseudolineArrangement
     222            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     223            sage: p = PseudolineArrangement(permutations)
     224            sage: matrix = p.felsner_matrix()
     225            sage: PseudolineArrangement(matrix) == p
     226            True
     227
     228        TESTS:
     229
     230        Wrong input::
     231
     232            sage: PseudolineArrangement([[5, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]])
     233            Traceback (most recent call last):
     234            ...
     235            ValueError: Are the lines really numbered from 0 to n-1?
     236            sage: PseudolineArrangement([(3, 2), (3, 1), (0, 3), (2, 1), (0, 2)])
     237            Traceback (most recent call last):
     238            ...
     239            ValueError: A line is numbered 3 but the number of transpositions ...
     240        """
     241
     242        # Sequence of transpositions
     243        if (encoding == "transpositions" or
     244            (encoding == "auto" and len(seq[0]) == 2 and len(seq) > 3)):
     245
     246            self._n = max(map(max, seq)) + 1
     247            if (self._n * (self._n-1))/2 != len(seq):
     248                raise ValueError(
     249                    "A line is numbered "+str(self._n-1)+" but the number"+
     250                    " of transpositions is different from binomial("+
     251                    str(self._n-1)+",2). Are the lines numbered from 0 to n-1?"+
     252                    " Are they really non-parallel? Please check the documentation.")
     253
     254            self._permutations = [[] for i in range(self._n)]
     255
     256            for i,j in seq:
     257                self._permutations[i].append(j)
     258                self._permutations[j].append(i)
     259
     260        # Sequence of permutations
     261        elif (encoding == "permutations" or
     262            (encoding == "auto" and (len(seq[0]) == len(seq)-1) and max(seq[0]) > 1)):
     263
     264            self._n = len(seq)
     265            self._permutations = map(list,seq)
     266
     267            if max(map(max, seq)) != self._n -1 :
     268                raise ValueError("Are the lines really numbered from 0 to n-1?")
     269
     270        # Felsner encoding
     271        elif (encoding == "Felsner" or
     272            (encoding == "auto" and len(seq[0]) == len(seq) -1)):
     273
     274            seq = deepcopy(seq)
     275            self._n = len(seq)
     276            ordering = range(self._n)
     277
     278            self._permutations = [[] for i in range(self._n)]
     279
     280            crossings = (self._n * (self._n-1))/2
     281
     282            i = 0
     283            while crossings > 0:
     284                if (seq[i] != [] and
     285                    (seq[i][0] == 0 and
     286                     seq[i+1][0] == 1)):
     287
     288                    crossings -= 1
     289
     290                    self._permutations[ordering[i]].append(ordering[i+1])
     291                    self._permutations[ordering[i+1]].append(ordering[i])
     292
     293                    ordering[i], ordering[i+1] = ordering[i+1], ordering[i]
     294                    seq[i], seq[i+1] = seq[i+1], seq[i]
     295
     296                    seq[i].pop(0)
     297                    seq[i+1].pop(0)
     298
     299                    if i > 0 and seq[i-1] is not []:
     300                        i -= 1
     301                    else:
     302                        i += 1
     303                else:
     304                    i += 1
     305        else:
     306
     307            if encoding != "auto":
     308                raise ValueError("The value of encoding must be one of 'transpositions', 'permutations', 'Felsner' or 'auto'.")
     309
     310            raise ValueError("The encoding you used could not be guessed. Your input string is probably badly formatted, or you have at most 3 lines and we cannot distinguish the encoding. Please specify the encoding you used.")
     311
     312    def transpositions(self):
     313        r"""
     314        Returns the arrangement as `\binom n 2` transpositions.
     315
     316        See the :mod:`pseudolines module <sage.geometry.pseudolines>`'s
     317        documentation for more information on this encoding.
     318
     319        EXAMPLE::
     320
     321            sage: from sage.geometry.pseudolines import PseudolineArrangement
     322            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     323            sage: p1 = PseudolineArrangement(permutations)
     324            sage: transpositions = [(3, 2), (3, 1), (0, 3), (2, 1), (0, 2), (0, 1)]
     325            sage: p2 = PseudolineArrangement(transpositions)
     326            sage: p1 == p2
     327            True
     328            sage: p1.transpositions()
     329            [(3, 2), (3, 1), (0, 3), (2, 1), (0, 2), (0, 1)]
     330            sage: p2.transpositions()
     331            [(3, 2), (3, 1), (0, 3), (2, 1), (0, 2), (0, 1)]
     332        """
     333        t = []
     334        perm = deepcopy(self._permutations)
     335
     336        crossings = (self._n * (self._n-1))/2
     337
     338        while crossings > 0:
     339
     340            i = 0
     341
     342            while perm[i] == []:
     343                i += 1
     344
     345            k = 0
     346            while i != perm[perm[i][0]][0]:
     347                i = perm[i][0]
     348                k+= 1
     349
     350                if k > self._n:
     351                    raise ValueError(
     352                        "It looks like the data does not correspond to a"+
     353                        "pseudoline arrangement. We have found k>2 lines"+
     354                        "such that the ith line meets the (i+1)th before"+
     355                        " the (i-1)th (this creates a cyclic dependency)"+
     356                        " which is totally impossible.")
     357
     358            t.append((i, perm[i][0]))
     359            perm[perm[i][0]].pop(0)
     360            perm[i].pop(0)
     361
     362            crossings -= 1
     363
     364        if max(map(len,perm)) != 0:
     365            raise ValueError("There has been an error while computing the transpositions.")
     366
     367        return t
     368
     369    def permutations(self):
     370        r"""
     371        Returns the arrangements as `n` permutations of size `n-1`.
     372
     373        See the :mod:`pseudolines module <sage.geometry.pseudolines>`'s
     374        documentation for more information on this encoding.
     375
     376        EXAMPLE::
     377
     378            sage: from sage.geometry.pseudolines import PseudolineArrangement
     379            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     380            sage: p = PseudolineArrangement(permutations)
     381            sage: p.permutations()
     382            [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     383        """
     384        return deepcopy(self._permutations)
     385
     386    def felsner_matrix(self):
     387        r"""
     388        Returns a Felsner matrix describing the arrangement.
     389
     390        See the :mod:`pseudolines module <sage.geometry.pseudolines>`'s
     391        documentation for more information on this encoding.
     392
     393        EXAMPLE::
     394
     395            sage: from sage.geometry.pseudolines import PseudolineArrangement
     396            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     397            sage: p = PseudolineArrangement(permutations)
     398            sage: p.felsner_matrix()
     399            [[0, 0, 0], [0, 0, 1], [0, 1, 1], [1, 1, 1]]
     400        """
     401
     402        m = [[] for i in range(self._n)]
     403
     404        for i,j in self.transpositions():
     405            if i < j:
     406                i, j = j, i
     407
     408            m[j].append(0)
     409            m[i].append(1)
     410
     411        return m
     412
     413    def show(self, **args):
     414        r"""
     415        Displays the pseudoline arrangement as a wiring diagram.
     416
     417        INPUT:
     418
     419        - ``**args`` -- any arguments to be forwarded to the ``show`` method. In
     420          particular, to tune the dimensions, use the ``figsize`` argument
     421          (example below).
     422
     423        EXAMPLE::
     424
     425            sage: from sage.geometry.pseudolines import PseudolineArrangement
     426            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     427            sage: p = PseudolineArrangement(permutations)
     428            sage: p.show(figsize=[7,5])
     429
     430        TESTS::
     431
     432            sage: from sage.geometry.pseudolines import PseudolineArrangement
     433            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 0, 1], [2, 0, 1]]
     434            sage: p = PseudolineArrangement(permutations)
     435            sage: p.show()
     436            Traceback (most recent call last):
     437            ...
     438            ValueError: There has been a problem while plotting the figure...
     439        """
     440
     441        crossings = []
     442
     443        ends = range(self._n)
     444
     445        for i,j in self.transpositions():
     446            iy = ends[i]
     447            jy = ends[j]
     448
     449            crossings.append(max(jy,iy))
     450
     451            ends[i] = jy
     452            ends[j] = iy
     453
     454            if abs(iy-jy) != 1:
     455                raise ValueError(
     456                    "There has been a problem while plotting the figure. It "+
     457                    "seems that the lines are not correctly ordered. Please "+
     458                    "check the pseudolines modules documentation, there is a "
     459                    +"warning about that.")
     460
     461        from sage.plot.braid_plot import braid_plot
     462        braid_plot(self._n, crossings, orientation="left-right").show(**args)
     463
     464    def __repr__(self):
     465        r"""
     466        A short txt description of the pseudoline arrangement.
     467
     468        EXAMPLE::
     469
     470            sage: from sage.geometry.pseudolines import PseudolineArrangement
     471            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     472            sage: p = PseudolineArrangement(permutations)
     473            sage: p
     474            Arrangement of pseudolines of size 4
     475        """
     476        return "Arrangement of pseudolines of size "+str(self._n)
     477
     478    def __eq__(self, other):
     479        r"""
     480        Test of equality.
     481
     482        TEST::
     483
     484            sage: from sage.geometry.pseudolines import PseudolineArrangement
     485            sage: permutations = [[3, 2, 1], [3, 2, 0], [3, 1, 0], [2, 1, 0]]
     486            sage: p1 = PseudolineArrangement(permutations)
     487            sage: transpositions = [(3, 2), (3, 1), (0, 3), (2, 1), (0, 2), (0, 1)]
     488            sage: p2 = PseudolineArrangement(transpositions)
     489            sage: p1 == p2
     490            True
     491        """
     492        return (self._n == other._n) and (self._permutations == other._permutations)
  • new file sage/plot/braid_plot.py

    diff --git a/sage/plot/braid_plot.py b/sage/plot/braid_plot.py
    new file mode 100644
    - +  
     1r"""
     2Braid plots
     3
     4This module implements a plotting function for braids. It is used in the
     5:mod:`Braid Group module <sage.groups.braid>`.
     6
     7Function
     8--------
     9"""
     10
     11def braid_plot(n, crossings, color='rainbow',
     12               orientation='bottom-top', gap=0.05,
     13               aspect_ratio=1, axes=False, **kwds):
     14    """
     15    Plot the braid
     16
     17    INPUT:
     18
     19    - ``n`` (integer) -- the number of strands
     20
     21    - ``crossings`` -- ordered list of integers among `\{1,...,n-1\}` describing
     22      the crossings of the braid. An integer `i` in this list means that the two
     23      lines at depth `i` and `i-1` should cross.
     24
     25    - ``color`` -- (default: ``'rainbow'``) the color of the
     26      strands. Possible values are:
     27
     28        * ``'rainbow'``, uses :meth:`~sage.plot.colors.rainbow`
     29          according to the number of strands.
     30
     31        * a valid color name for :meth:`~sage.plot.bezier_path`
     32          and :meth:`~sage.plot.line`. Used for all strands.
     33
     34        * a list or a tuple of colors for each individual strand.
     35
     36    - ``orientation`` -- (default: ``'bottom-top'``) determines how
     37      the braid is printed. The possible values are:
     38
     39        * ``'bottom-top'``, the braid is printed from bottom to top
     40
     41        * ``'top-bottom'``, the braid is printed from top to bottom
     42
     43        * ``'left-right'``, the braid is printed from left to right
     44
     45    - ``gap`` -- floating point number (default: 0.05). determines
     46      the size of the gap left when a strand goes under another.
     47
     48    - ``aspect_ratio`` -- floating point number (default:
     49      ``1``). The aspect ratio.
     50
     51    - ``**kwds`` -- other keyword options that are passed to
     52      :meth:`~sage.plot.bezier_path` and :meth:`~sage.plot.line`.
     53
     54    EXAMPLES::
     55
     56        sage: B = BraidGroup(4, 's')
     57        sage: b = B([1, 2, 3, 1, 2, 1])
     58        sage: b.plot()
     59        sage: b.plot(color=["red", "blue", "red", "blue"])
     60
     61        sage: B.<s,t> = BraidGroup(3)
     62        sage: b = t^-1*s^2
     63        sage: b.plot(orientation="left-right", color="red")
     64    """
     65    from sage.plot.bezier_path import bezier_path
     66    from sage.plot.plot import Graphics, line
     67    from sage.plot.colors import rainbow
     68    if orientation=='top-bottom':
     69        orx = 0
     70        ory = -1
     71        nx = 1
     72        ny = 0
     73    elif orientation=='left-right':
     74        orx = 1
     75        ory = 0
     76        nx = 0
     77        ny = -1
     78    elif orientation=='bottom-top':
     79        orx = 0
     80        ory = 1
     81        nx = 1
     82        ny = 0
     83    else:
     84        raise ValueError('unknown value for "orientation"')
     85
     86    if isinstance(color, (list, tuple)):
     87        if len(color) != n:
     88            raise TypeError("color (=%s) must contain exactly %d colors" % (color, n))
     89        col = list(color)
     90    elif color == "rainbow":
     91        col = rainbow(n)
     92    else:
     93        col = [color]*n
     94
     95    r = lambda i,j : (j*nx+i*orx, i*ory+j*ny)
     96
     97    a = Graphics()
     98    op = gap
     99
     100    for i, m in enumerate(crossings):
     101        for j in range(n):
     102            if m==j+1:
     103                a += bezier_path([[r(i,j), r(i+0.25,j), r(i+.5,j+.5)],
     104                                  [r(i+0.75,j+1),r(i+1,j+1)]], color=col[j], **kwds)
     105            elif m==j:
     106                a += bezier_path([[r(i,j), r(i+.25,j), r(i+0.5-2*op,j-0.5+4*op),
     107                                   r(i+0.5-op,j-0.5+2*op)]],
     108                                 color=col[j], **kwds)
     109                a += bezier_path([[r(i+0.5+op, j-0.5-2*op),
     110                                   r(i+0.5+2*op,j-0.5-4*op),
     111                                   r(i+0.75,j-1),
     112                                   r(i+1,j-1)]], color=col[j], **kwds)
     113                col[j], col[j-1] = col[j-1], col[j]
     114            elif -m==j+1:
     115                raise Exception
     116                a += bezier_path([[r(i,j), r(i+.25,j),
     117                                   r(i+0.5-2*op, j+0.5-4*op),
     118                                   r(i+0.5-op,j+0.5-2*op)]],
     119                                 color=col[j], **kwds)
     120                a += bezier_path([[r(i+0.5+op,j+0.5+2*op),
     121                                   r(i+0.5+2*op,j+0.5+4*op),
     122                                   r(i+0.75,j+1),
     123                                   r(i+1,j+1)]], color=col[j], **kwds)
     124            elif -m==j:
     125                raise Exception
     126                a += bezier_path([[r(i,j), r(i+.25,j),
     127                                   r(i+0.5,j-0.5)],
     128                                  [r(i+0.75,j-1),
     129                                   r(i+1,j-1)]], color=col[j], **kwds)
     130
     131                col[j], col[j-1] = col[j-1], col[j]
     132            else:
     133                a += line([r(i,j), r(i+1,j)], color=col[j], **kwds)
     134
     135    a.set_aspect_ratio(aspect_ratio)
     136    a.axes(axes)
     137    return a