Example #1
0
    def to_undirected_graph(self):
        r"""
        Return the undirected graph obtained from the tree nodes and edges.

        The graph is endowed with an embedding, so that it will be displayed
        correctly.

        EXAMPLES::

            sage: t = OrderedTree([])
            sage: t.to_undirected_graph()
            Graph on 1 vertex
            sage: t = OrderedTree([[[]],[],[]])
            sage: t.to_undirected_graph()
            Graph on 5 vertices

        If the tree is labelled, we use its labelling to label the graph. This
        will fail if the labels are not all distinct.
        Otherwise, we use the graph canonical labelling which means that
        two different trees can have the same graph.

        EXAMPLES::

            sage: t = OrderedTree([[[]],[],[]])
            sage: t.canonical_labelling().to_undirected_graph()
            Graph on 5 vertices

        TESTS::

            sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph()
            False
            sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph()
            True
            sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph()
            False
        """
        from sage.graphs.graph import Graph
        g = Graph()
        if self in LabelledOrderedTrees():
            relabel = False
        else:
            self = self.canonical_labelling()
            relabel = True
        roots = [self]
        g.add_vertex(name=self.label())
        emb = {self.label(): []}
        while roots:
            node = roots.pop()
            children = reversed([child.label() for child in node])
            emb[node.label()].extend(children)
            for child in node:
                g.add_vertex(name=child.label())
                emb[child.label()] = [node.label()]
                g.add_edge(child.label(), node.label())
                roots.append(child)
        g.set_embedding(emb)
        if relabel:
            g = g.canonical_label()
        return g
Example #2
0
    def to_undirected_graph(self):
        r"""
        Return the undirected graph obtained from the tree nodes and edges.

        The graph is endowed with an embedding, so that it will be displayed
        correctly.

        EXAMPLES::

            sage: t = OrderedTree([])
            sage: t.to_undirected_graph()
            Graph on 1 vertex
            sage: t = OrderedTree([[[]],[],[]])
            sage: t.to_undirected_graph()
            Graph on 5 vertices

        If the tree is labelled, we use its labelling to label the graph. This
        will fail if the labels are not all distinct.
        Otherwise, we use the graph canonical labelling which means that
        two different trees can have the same graph.

        EXAMPLES::

            sage: t = OrderedTree([[[]],[],[]])
            sage: t.canonical_labelling().to_undirected_graph()
            Graph on 5 vertices

        TESTS::

            sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph()
            False
            sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph()
            True
            sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph()
            False
        """
        from sage.graphs.graph import Graph
        g = Graph()
        if self in LabelledOrderedTrees():
            relabel = False
        else:
            self = self.canonical_labelling()
            relabel = True
        roots = [self]
        g.add_vertex(name=self.label())
        emb = {self.label(): []}
        while roots:
            node = roots.pop()
            children = reversed([child.label() for child in node])
            emb[node.label()].extend(children)
            for child in node:
                g.add_vertex(name=child.label())
                emb[child.label()] = [node.label()]
                g.add_edge(child.label(), node.label())
                roots.append(child)
        g.set_embedding(emb)
        if relabel:
            g = g.canonical_label()
        return g
Example #3
0
def _spanning_tree(base, verts):
    graph = Graph([list(verts), lambda x, y: x is not y])

    def length(edge):
        x, y, _ = edge
        return abs(CC(x.alg) - CC(y.alg))

    tree = graph.min_spanning_tree(length)
    tree = Graph(tree)
    tree.add_vertex(base)
    return tree
Example #4
0
def CayleyGraph(vspace, gens, directed=False):
  """
  Generate a Cayley Graph over given vector space with edges
  generated by given generators. The graph is optionally directed.
  Try e.g. CayleyGraph(VectorSpace(GF(2), 3), [(1,0,0), (0,1,0), (0,0,1)])
  """
  G = Graph()
  for v in vspace:
    G.add_vertex(tuple(v))
    for g in gens:
      g2 = vspace(g)
      G.add_edge(tuple(v), tuple(v + g2))
  return G
Example #5
0
def CayleyGraph(vspace, gens, directed=False):
    """
  Generate a Cayley Graph over given vector space with edges
  generated by given generators. The graph is optionally directed.
  Try e.g. CayleyGraph(VectorSpace(GF(2), 3), [(1,0,0), (0,1,0), (0,0,1)])
  """
    G = Graph()
    for v in vspace:
        G.add_vertex(tuple(v))
        for g in gens:
            g2 = vspace(g)
            G.add_edge(tuple(v), tuple(v + g2))
    return G
Example #6
0
    def to_undirected_graph(self):
        r"""
        Return the undirected graph obtained from the tree nodes and edges.

        EXAMPLES::

            sage: t = OrderedTree([])
            sage: t.to_undirected_graph()
            Graph on 1 vertex
            sage: t = OrderedTree([[[]],[],[]])
            sage: t.to_undirected_graph()
            Graph on 5 vertices

        If the tree is labelled, we use its labelling to label the graph.
        Otherwise, we use the graph canonical labelling which means that
        two different trees can have the same graph.

        EXAMPLES::

            sage: t = OrderedTree([[[]],[],[]])
            sage: t.canonical_labelling().to_undirected_graph()
            Graph on 5 vertices
            sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph()
            False
            sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph()
            True
            sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph()
            False
        """
        from sage.graphs.graph import Graph

        g = Graph()
        if self in LabelledOrderedTrees():
            relabel = False
        else:
            self = self.canonical_labelling()
            relabel = True
        roots = [self]
        g.add_vertex(name=self.label())
        while len(roots) != 0:
            node = roots.pop()
            for child in node:
                g.add_vertex(name=child.label())
                g.add_edge(child.label(), node.label())
                roots.append(child)
        if relabel:
            g = g.canonical_label()
        return g
Example #7
0
    def to_undirected_graph(self):
        r"""
        Return the undirected graph obtained from the tree nodes and edges.

        EXAMPLES::

            sage: t = OrderedTree([])
            sage: t.to_undirected_graph()
            Graph on 1 vertex
            sage: t = OrderedTree([[[]],[],[]])
            sage: t.to_undirected_graph()
            Graph on 5 vertices

        If the tree is labelled, we use its labelling to label the graph.
        Otherwise, we use the graph canonical labelling which means that
        two different trees can have the same graph.

        EXAMPLES::

            sage: t = OrderedTree([[[]],[],[]])
            sage: t.canonical_labelling().to_undirected_graph()
            Graph on 5 vertices
            sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph()
            False
            sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph()
            True
            sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph()
            False
        """
        from sage.graphs.graph import Graph
        g = Graph()
        if self in LabelledOrderedTrees():
            relabel = False
        else:
            self = self.canonical_labelling()
            relabel = True
        roots = [self]
        g.add_vertex(name=self.label())
        while len(roots) != 0:
            node = roots.pop()
            for child in node:
                g.add_vertex(name=child.label())
                g.add_edge(child.label(), node.label())
                roots.append(child)
        if (relabel):
            g = g.canonical_label()
        return g
Example #8
0
    def graph(self):
        r"""
        EXAMPLES::

            sage: from slabbe import DoubleRootedTree
            sage: edges = [(0,5),(1,2),(2,6),(3,2),(4,1),(5,7),(7,1),(8,2),(9,4)]
            sage: D = DoubleRootedTree(edges, 6, 0)
            sage: D.graph()
            Graph on 10 vertices
        """
        if self._graph is None:
            G = Graph()
            G.add_vertex(self._rootA)
            G.add_vertex(self._rootB)
            for a,b in self._edges:
                G.add_edge(a,b)
            self._graph = G
        return self._graph
Example #9
0
def GridGraph(dim_list):
    """
    Returns an n-dimensional grid graph.

    INPUT:


    -  ``dim_list`` - a list of integers representing the
       number of nodes to extend in each dimension.


    PLOTTING: When plotting, this graph will use the default
    spring-layout algorithm, unless a position dictionary is
    specified.

    EXAMPLES::

        sage: G = graphs.GridGraph([2,3,4])
        sage: G.show()  # long time

    ::

        sage: C = graphs.CubeGraph(4)
        sage: G = graphs.GridGraph([2,2,2,2])
        sage: C.show()  # long time
        sage: G.show()  # long time

    TESTS:

    The graph name contains the dimension::

        sage: g = graphs.GridGraph([5, 7])
        sage: g.name()
        'Grid Graph for [5, 7]'
        sage: g = graphs.GridGraph([2, 3, 4])
        sage: g.name()
        'Grid Graph for [2, 3, 4]'
        sage: g = graphs.GridGraph([2, 4, 3])
        sage: g.name()
        'Grid Graph for [2, 4, 3]'

    One dimensional grids (i.e., path) have simple vertex labels::

        sage: g = graphs.GridGraph([5])
        sage: g.vertices()
        [0, 1, 2, 3, 4]

    The graph is correct::

        sage: dim = [randint(1,4) for i in range(4)]
        sage: g = graphs.GridGraph(dim)
        sage: import networkx
        sage: h = Graph( networkx.grid_graph(list(dim)) )
        sage: g.is_isomorphic(h)
        True

    Trivial cases::

        sage: g = graphs.GridGraph([]); g; g.vertices()
        Grid Graph for []: Graph on 0 vertices
        []
        sage: g = graphs.GridGraph([1]); g; g.vertices()
        Grid Graph for [1]: Graph on 1 vertex
        [0]
        sage: g = graphs.GridGraph([2]); g; g.vertices()
        Grid Graph for [2]: Graph on 2 vertices
        [0, 1]
        sage: g = graphs.GridGraph([1,1]); g; g.vertices()
        Grid Graph for [1, 1]: Graph on 1 vertex
        [(0, 0)]
        sage: g = graphs.GridGraph([1, 1, 1]); g; g.vertices()
        Grid Graph for [1, 1, 1]: Graph on 1 vertex
        [(0, 0, 0)]
        sage: g = graphs.GridGraph([1,1,2]); g; g.vertices()
        Grid Graph for [1, 1, 2]: Graph on 2 vertices
        [(0, 0, 0), (0, 0, 1)]

    All dimensions must be positive integers::

        sage: g = graphs.GridGraph([2,-1,3])
        Traceback (most recent call last):
        ...
        ValueError: All dimensions must be positive integers !
    """
    dim = [int(a) for a in dim_list]
    if any(a <= 0 for a in dim):
        raise ValueError("All dimensions must be positive integers !")

    g = Graph()
    n_dim = len(dim)
    if n_dim==1:
        # Vertices are labeled from 0 to dim[0]-1
        g = PathGraph(dim[0])
    elif n_dim==2:
        # We use the Grid2dGraph generator to also get the positions
        g = Grid2dGraph(*dim)
    elif n_dim>2:
        # Vertices are tuples of dimension n_dim, and the graph contains at
        # least vertex (0, 0, ..., 0)
        g.add_vertex(tuple([0]*n_dim))
        import itertools
        for u in itertools.product(*[range(d) for d in dim]):
            for i in range(n_dim):
                if u[i]+1<dim[i]:
                    v = list(u)
                    v[i] = u[i]+1
                    g.add_edge(u, tuple(v))

    g.name("Grid Graph for {}".format(dim))
    return g
Example #10
0
def ChessboardGraphGenerator(dim_list,
                             rook = True,    rook_radius = None,
                             bishop = True,  bishop_radius = None,
                             knight = True, knight_x = 1, knight_y = 2,
                             relabel = False):
    r"""
    Returns a Graph built on a `d`-dimensional chessboard with prescribed
    dimensions and interconnections.

    This function allows to generate many kinds of graphs corresponding to legal
    movements on a `d`-dimensional chessboard: Queen Graph, King Graph, Knight
    Graphs, Bishop Graph, and many generalizations. It also allows to avoid
    redondant code.

    INPUT:

    - ``dim_list`` -- an iterable object (list, set, dict) providing the
      dimensions `n_1, n_2, \ldots, n_d`, with `n_i \geq 1`, of the chessboard.

    - ``rook`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move as a rook, that is at any distance along a
      dimension.

    - ``rook_radius`` -- (default: None) integer value restricting the rook-like
      movements to distance at most `rook_radius`.

    - ``bishop`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move like a bishop, that is along diagonals.

    - ``bishop_radius`` -- (default: None) integer value restricting the
      bishop-like movements to distance at most `bishop_radius`.

    - ``knight`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move like a knight.

    - ``knight_x`` -- (default: 1) integer indicating the number on steps the
      chess piece moves in one dimension when moving like a knight.

    - ``knight_y`` -- (default: 2) integer indicating the number on steps the
      chess piece moves in the second dimension when moving like a knight.

    - ``relabel`` -- (default: ``False``) a boolean set to ``True`` if vertices
      must be relabeled as integers.

    OUTPUT:

    - A Graph build on a `d`-dimensional chessboard with prescribed dimensions,
      and with edges according given parameters.

    - A string encoding the dimensions. This is mainly useful for providing
      names to graphs.

    EXAMPLES:

    A `(2,2)`-King Graph is isomorphic to the complete graph on 4 vertices::

        sage: G, _ = graphs.ChessboardGraphGenerator( [2,2] )
        sage: G.is_isomorphic( graphs.CompleteGraph(4) )
        True

    A Rook's Graph in 2 dimensions is isomporphic to the Cartesian product of 2
    complete graphs::

        sage: G, _ = graphs.ChessboardGraphGenerator( [3,4], rook=True, rook_radius=None, bishop=False, knight=False )
        sage: H = ( graphs.CompleteGraph(3) ).cartesian_product( graphs.CompleteGraph(4) )
        sage: G.is_isomorphic(H)
        True

    TESTS:

    Giving dimensions less than 2::

        sage: graphs.ChessboardGraphGenerator( [0, 2] )
        Traceback (most recent call last):
        ...
        ValueError: The dimensions must be positive integers larger than 1.

    Giving non integer dimensions::

        sage: graphs.ChessboardGraphGenerator( [4.5, 2] )
        Traceback (most recent call last):
        ...
        ValueError: The dimensions must be positive integers larger than 1.

    Giving too few dimensions::

        sage: graphs.ChessboardGraphGenerator( [2] )
        Traceback (most recent call last):
        ...
        ValueError: The chessboard must have at least 2 dimensions.

    Giving a non-iterable object as first parameter::

        sage: graphs.ChessboardGraphGenerator( 2, 3 )
        Traceback (most recent call last):
        ...
        TypeError: The first parameter must be an iterable object.

    Giving too small rook radius::

        sage: graphs.ChessboardGraphGenerator( [2, 3], rook=True, rook_radius=0 )
        Traceback (most recent call last):
        ...
        ValueError: The rook_radius must be either None or have an integer value >= 1.

    Giving wrong values for knights movements::

        sage: graphs.ChessboardGraphGenerator( [2, 3], rook=False, bishop=False, knight=True, knight_x=1, knight_y=-1 )
        Traceback (most recent call last):
        ...
        ValueError: The knight_x and knight_y values must be integers of value >= 1.
    """
    from sage.rings.integer_ring import ZZ

    # We decode the dimensions of the chessboard
    try:
        dim = list(dim_list)
        nb_dim = len(dim)
    except TypeError:
        raise TypeError('The first parameter must be an iterable object.')
    if nb_dim < 2:
        raise ValueError('The chessboard must have at least 2 dimensions.')
    if any(a not in ZZ or a < 1 for a in dim):
        raise ValueError('The dimensions must be positive integers larger than 1.')
    dimstr = str(tuple(dim))

    # We check the radius toward neighbors
    if rook:
        if rook_radius is None:
            rook_radius = max(dim)
        elif not rook_radius in ZZ or rook_radius < 1:
            raise ValueError('The rook_radius must be either None or have an integer value >= 1.')
    if bishop:
        if bishop_radius is None:
            bishop_radius = max(dim)
        elif not bishop_radius in ZZ or bishop_radius < 1:
            raise ValueError('The bishop_radius must be either None or have an integer value >= 1.')
    if knight and ( not knight_x in ZZ or not knight_y in ZZ or knight_x < 1 or knight_y < 1 ):
        raise ValueError('The knight_x and knight_y values must be integers of value >= 1.')

    # We build the set of vertices of the d-dimensionnal chessboard
    from itertools import product
    V = [list(x) for x in list(product(*[range(_) for _ in dim]))]

    from sage.combinat.combination import Combinations
    combin = Combinations(range(nb_dim),2)

    from sage.graphs.graph import Graph
    G = Graph()
    for u in V:
        uu = tuple(u)
        G.add_vertex(uu)

        if rook:
            # We add edges to vertices we can reach when moving in one dimension
            for d in xrange(nb_dim):
                v = u[:]
                for k in xrange(v[d]+1, min(dim[d],v[d]+1+rook_radius)):
                    v[d] = k
                    G.add_edge( uu, tuple(v) )

        if bishop or knight:
            # We add edges to vertices we can reach when moving in two dimensions
            for dx,dy in combin:
                n = dim[dx]
                m = dim[dy]
                v = u[:]
                i = u[dx]
                j = u[dy]

                if bishop:
                    # Diagonal
                    for k in xrange(1, min(n-i,m-j,bishop_radius+1)):
                        v[dx] = i+k
                        v[dy] = j+k
                        G.add_edge( uu, tuple(v) )

                    # Anti-diagonal
                    for k in xrange(min(i, m-j-1, bishop_radius)):
                        v[dx] = i-k-1
                        v[dy] = j+k+1
                        G.add_edge( uu, tuple(v) )

                if knight:
                    # Moving knight_x in one dimension and knight_y in another
                    # dimension
                    if i+knight_y < n:
                        if j+knight_x < m:
                            v[dx] = i+knight_y
                            v[dy] = j+knight_x
                            G.add_edge( uu, tuple(v) )
                        if j-knight_x >= 0:
                            v[dx] = i+knight_y
                            v[dy] = j-knight_x
                            G.add_edge( uu, tuple(v) )
                    if j+knight_y < m:
                        if i+knight_x < n:
                            v[dx] = i+knight_x
                            v[dy] = j+knight_y
                            G.add_edge( uu, tuple(v) )
                        if i-knight_x >= 0:
                            v[dx] = i-knight_x
                            v[dy] = j+knight_y
                            G.add_edge( uu, tuple(v) )

    if relabel:
        G.relabel( inplace=True )
    return G, dimstr
Example #11
0
File: basic.py Project: drupel/sage
def GridGraph(dim_list):
    """
    Returns an n-dimensional grid graph.

    INPUT:


    -  ``dim_list`` - a list of integers representing the
       number of nodes to extend in each dimension.


    PLOTTING: When plotting, this graph will use the default
    spring-layout algorithm, unless a position dictionary is
    specified.

    EXAMPLES::

        sage: G = graphs.GridGraph([2,3,4])
        sage: G.show()  # long time

    ::

        sage: C = graphs.CubeGraph(4)
        sage: G = graphs.GridGraph([2,2,2,2])
        sage: C.show()  # long time
        sage: G.show()  # long time

    TESTS:

    The graph name contains the dimension::

        sage: g = graphs.GridGraph([5, 7])
        sage: g.name()
        'Grid Graph for [5, 7]'
        sage: g = graphs.GridGraph([2, 3, 4])
        sage: g.name()
        'Grid Graph for [2, 3, 4]'
        sage: g = graphs.GridGraph([2, 4, 3])
        sage: g.name()
        'Grid Graph for [2, 4, 3]'

    One dimensional grids (i.e., path) have simple vertex labels::

        sage: g = graphs.GridGraph([5])
        sage: g.vertices()
        [0, 1, 2, 3, 4]

    The graph is correct::

        sage: dim = [randint(1,4) for i in range(4)]
        sage: g = graphs.GridGraph(dim)
        sage: import networkx
        sage: h = Graph( networkx.grid_graph(list(dim)) )
        sage: g.is_isomorphic(h)
        True

    Trivial cases::

        sage: g = graphs.GridGraph([]); g; g.vertices()
        Grid Graph for []: Graph on 0 vertices
        []
        sage: g = graphs.GridGraph([1]); g; g.vertices()
        Grid Graph for [1]: Graph on 1 vertex
        [0]
        sage: g = graphs.GridGraph([2]); g; g.vertices()
        Grid Graph for [2]: Graph on 2 vertices
        [0, 1]
        sage: g = graphs.GridGraph([1,1]); g; g.vertices()
        Grid Graph for [1, 1]: Graph on 1 vertex
        [(0, 0)]
        sage: g = graphs.GridGraph([1, 1, 1]); g; g.vertices()
        Grid Graph for [1, 1, 1]: Graph on 1 vertex
        [(0, 0, 0)]
        sage: g = graphs.GridGraph([1,1,2]); g; g.vertices()
        Grid Graph for [1, 1, 2]: Graph on 2 vertices
        [(0, 0, 0), (0, 0, 1)]

    All dimensions must be positive integers::

        sage: g = graphs.GridGraph([2,-1,3])
        Traceback (most recent call last):
        ...
        ValueError: All dimensions must be positive integers !
    """
    dim = [int(a) for a in dim_list]
    if any(a <= 0 for a in dim):
        raise ValueError("All dimensions must be positive integers !")

    g = Graph()
    n_dim = len(dim)
    if n_dim==1:
        # Vertices are labeled from 0 to dim[0]-1
        g = PathGraph(dim[0])
    elif n_dim==2:
        # We use the Grid2dGraph generator to also get the positions
        g = Grid2dGraph(*dim)
    elif n_dim>2:
        # Vertices are tuples of dimension n_dim, and the graph contains at
        # least vertex (0, 0, ..., 0)
        g.add_vertex(tuple([0]*n_dim))
        import itertools
        for u in itertools.product(*[range(d) for d in dim]):
            for i in range(n_dim):
                if u[i]+1<dim[i]:
                    v = list(u)
                    v[i] = u[i]+1
                    g.add_edge(u, tuple(v))

    g.name("Grid Graph for {}".format(dim))
    return g
Example #12
0
def ChessboardGraphGenerator(dim_list,
                             rook=True,
                             rook_radius=None,
                             bishop=True,
                             bishop_radius=None,
                             knight=True,
                             knight_x=1,
                             knight_y=2,
                             relabel=False):
    r"""
    Returns a Graph built on a `d`-dimensional chessboard with prescribed
    dimensions and interconnections.

    This function allows to generate many kinds of graphs corresponding to legal
    movements on a `d`-dimensional chessboard: Queen Graph, King Graph, Knight
    Graphs, Bishop Graph, and many generalizations. It also allows to avoid
    redondant code.

    INPUT:

    - ``dim_list`` -- an iterable object (list, set, dict) providing the
      dimensions `n_1, n_2, \ldots, n_d`, with `n_i \geq 1`, of the chessboard.

    - ``rook`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move as a rook, that is at any distance along a
      dimension.

    - ``rook_radius`` -- (default: None) integer value restricting the rook-like
      movements to distance at most `rook_radius`.

    - ``bishop`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move like a bishop, that is along diagonals.

    - ``bishop_radius`` -- (default: None) integer value restricting the
      bishop-like movements to distance at most `bishop_radius`.

    - ``knight`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move like a knight.

    - ``knight_x`` -- (default: 1) integer indicating the number on steps the
      chess piece moves in one dimension when moving like a knight.

    - ``knight_y`` -- (default: 2) integer indicating the number on steps the
      chess piece moves in the second dimension when moving like a knight.

    - ``relabel`` -- (default: ``False``) a boolean set to ``True`` if vertices
      must be relabeled as integers.

    OUTPUT:

    - A Graph build on a `d`-dimensional chessboard with prescribed dimensions,
      and with edges according given parameters.

    - A string encoding the dimensions. This is mainly useful for providing
      names to graphs.

    EXAMPLES:

    A `(2,2)`-King Graph is isomorphic to the complete graph on 4 vertices::

        sage: G, _ = graphs.ChessboardGraphGenerator( [2,2] )
        sage: G.is_isomorphic( graphs.CompleteGraph(4) )
        True

    A Rook's Graph in 2 dimensions is isomporphic to the Cartesian product of 2
    complete graphs::

        sage: G, _ = graphs.ChessboardGraphGenerator( [3,4], rook=True, rook_radius=None, bishop=False, knight=False )
        sage: H = ( graphs.CompleteGraph(3) ).cartesian_product( graphs.CompleteGraph(4) )
        sage: G.is_isomorphic(H)
        True

    TESTS:

    Giving dimensions less than 2::

        sage: graphs.ChessboardGraphGenerator( [0, 2] )
        Traceback (most recent call last):
        ...
        ValueError: The dimensions must be positive integers larger than 1.

    Giving non integer dimensions::

        sage: graphs.ChessboardGraphGenerator( [4.5, 2] )
        Traceback (most recent call last):
        ...
        ValueError: The dimensions must be positive integers larger than 1.

    Giving too few dimensions::

        sage: graphs.ChessboardGraphGenerator( [2] )
        Traceback (most recent call last):
        ...
        ValueError: The chessboard must have at least 2 dimensions.

    Giving a non-iterable object as first parameter::

        sage: graphs.ChessboardGraphGenerator( 2, 3 )
        Traceback (most recent call last):
        ...
        TypeError: The first parameter must be an iterable object.

    Giving too small rook radius::

        sage: graphs.ChessboardGraphGenerator( [2, 3], rook=True, rook_radius=0 )
        Traceback (most recent call last):
        ...
        ValueError: The rook_radius must be either None or have an integer value >= 1.

    Giving wrong values for knights movements::

        sage: graphs.ChessboardGraphGenerator( [2, 3], rook=False, bishop=False, knight=True, knight_x=1, knight_y=-1 )
        Traceback (most recent call last):
        ...
        ValueError: The knight_x and knight_y values must be integers of value >= 1.
    """
    from sage.rings.integer_ring import ZZ

    # We decode the dimensions of the chessboard
    try:
        dim = list(dim_list)
        nb_dim = len(dim)
    except TypeError:
        raise TypeError('The first parameter must be an iterable object.')
    if nb_dim < 2:
        raise ValueError('The chessboard must have at least 2 dimensions.')
    if any(a not in ZZ or a < 1 for a in dim):
        raise ValueError(
            'The dimensions must be positive integers larger than 1.')
    dimstr = str(tuple(dim))

    # We check the radius toward neighbors
    if rook:
        if rook_radius is None:
            rook_radius = max(dim)
        elif not rook_radius in ZZ or rook_radius < 1:
            raise ValueError(
                'The rook_radius must be either None or have an integer value >= 1.'
            )
    if bishop:
        if bishop_radius is None:
            bishop_radius = max(dim)
        elif not bishop_radius in ZZ or bishop_radius < 1:
            raise ValueError(
                'The bishop_radius must be either None or have an integer value >= 1.'
            )
    if knight and (not knight_x in ZZ or not knight_y in ZZ or knight_x < 1
                   or knight_y < 1):
        raise ValueError(
            'The knight_x and knight_y values must be integers of value >= 1.')

    # We build the set of vertices of the d-dimensionnal chessboard
    from itertools import product
    V = [list(x) for x in list(product(*[range(_) for _ in dim]))]

    from sage.combinat.combination import Combinations
    combin = Combinations(range(nb_dim), 2)

    from sage.graphs.graph import Graph
    G = Graph()
    for u in V:
        uu = tuple(u)
        G.add_vertex(uu)

        if rook:
            # We add edges to vertices we can reach when moving in one dimension
            for d in xrange(nb_dim):
                v = u[:]
                for k in xrange(v[d] + 1, min(dim[d], v[d] + 1 + rook_radius)):
                    v[d] = k
                    G.add_edge(uu, tuple(v))

        if bishop or knight:
            # We add edges to vertices we can reach when moving in two dimensions
            for dx, dy in combin:
                n = dim[dx]
                m = dim[dy]
                v = u[:]
                i = u[dx]
                j = u[dy]

                if bishop:
                    # Diagonal
                    for k in xrange(1, min(n - i, m - j, bishop_radius + 1)):
                        v[dx] = i + k
                        v[dy] = j + k
                        G.add_edge(uu, tuple(v))

                    # Anti-diagonal
                    for k in xrange(min(i, m - j - 1, bishop_radius)):
                        v[dx] = i - k - 1
                        v[dy] = j + k + 1
                        G.add_edge(uu, tuple(v))

                if knight:
                    # Moving knight_x in one dimension and knight_y in another
                    # dimension
                    if i + knight_y < n:
                        if j + knight_x < m:
                            v[dx] = i + knight_y
                            v[dy] = j + knight_x
                            G.add_edge(uu, tuple(v))
                        if j - knight_x >= 0:
                            v[dx] = i + knight_y
                            v[dy] = j - knight_x
                            G.add_edge(uu, tuple(v))
                    if j + knight_y < m:
                        if i + knight_x < n:
                            v[dx] = i + knight_x
                            v[dy] = j + knight_y
                            G.add_edge(uu, tuple(v))
                        if i - knight_x >= 0:
                            v[dx] = i - knight_x
                            v[dy] = j + knight_y
                            G.add_edge(uu, tuple(v))

    if relabel:
        G.relabel(inplace=True)
    return G, dimstr
Example #13
0
def core_subgraph_generator(NCS, forbid_identifications={}):
    # NCS is an object from the NaturalCoreSubgraph class which holds all the information we need.
    # forbid_identifications is a dictionary.  Any foo:bar entries (foo is a vertex, bar is a set) will forbid foo from being identified with any element of bar.  When running check_all_realizations_of_configuration, forbid_identifications is empty.  When running check_all_realizations_from_expanded_c7a4x5x5_case, we will have added entries to forbid_identifications.
    # We use Observation ? to make a dictionary of all the forbidden identifications, in addition to what was given by forbid_identifications.  Then, we generate all identifications (partitions of the vertex set) which avoid these forbidden identifications.  For each identification, we check each of the three conditions from Observation ??.  For any identifications that make it through the three conditions, we yield the graph formed by the identification and a list of its faces.

    # forbidden_dict will be the dictionary of all the forbidden identifications.
    # Initialize each vertex to have any empty set of forbidden vertices.
    forbidden_dict = {x: set() for x in range(NCS.order)}

    # If we have manually entered any forbidden identifications, go ahead and add those first.
    for x in forbid_identifications.keys():
        forbidden_dict[x] |= set(forbid_identifications[x])

    # If we have an open central face, then we apply Observation ? to the vertices on the spine before moving on to the specified faces.
    if NCS.is_open:
        init_spec_face = 1  # This will tell a later loop to skip the central face.

        # Vertices on the spine cannot be identified which are too close.
        # Handle vertices close to the ends separately.
        # Note:  for any open configurations in the target set, the spine has at least four vertices.
        forbidden_dict[NCS.spine[0]] |= set(NCS.spine[:3])
        forbidden_dict[NCS.spine[1]] |= set(NCS.spine[:4])
        forbidden_dict[NCS.spine[-2]] |= set(NCS.spine[-4:])
        forbidden_dict[NCS.spine[-1]] |= set(NCS.spine[-3:])
        # Now handle all middle vertices on the spine.
        for i in range(2, len(NCS.spine) - 2):
            forbidden_dict[NCS.spine[i]] |= set(NCS.spine[i - 2:i + 3])

        # Furthermore, spine vertices which are joining two specified outside faces ('sandwiched' vertices) cannot be identified with anything else on the spine.
        sandwiched_vertices = {
            x
            for x in range(1, len(NCS.facial_length_list))
            if NCS.facial_length_list[x - 1] != 'x'
            and NCS.facial_length_list[x] != 'x'
        }
        for v in NCS.spine:
            if v in sandwiched_vertices:
                forbidden_dict[v] |= set(NCS.spine)
            else:
                forbidden_dict[v] |= set(sandwiched_vertices)
    else:  # If we have a specified central face length...
        init_spec_face = 0  # ...then we tell the next loop to include the central face.

    # Now, for all specified faces, we apply Observation ??.  No two vertices on the same specified face can be identified.
    for face in NCS.faces[init_spec_face:]:
        for v in face:
            forbidden_dict[v] |= set(face)

    # Now that we have finished prepared forbidden_dict, we feed it to the partition generator to inspect the identifications.

    # Initialize the printing variables.
    count = 0  # Counts the number of partitions being checked.
    countCS = 0  # Counts how many made it to be yielded as core subgraphs.
    print "#Partitions      #CS     Time (Cum.)"
    print "..........0        0     begin!"
    begin = time.clock()

    # restricted_partition_generator generates all identifications of the vertices (partitions of the vertex set).  Its output is a partition, stored as a list of lists of vertices.
    for partition in restricted_partition_generator(range(NCS.order),
                                                    NCS.order - 1,
                                                    forbidden_dict):
        count += 1
        if count % 10000 == 0:  # Print every so often -- more helpful for large configurations.
            print "." * max(11 - len(str(count)), 0) + str(count) + " " * max(
                9 - len(str(countCS)),
                0) + str(countCS) + "     " + timestring(time.clock() - begin)

        # We will want to move between the current labelings of the initial plane subgraph and the new graph formed by the identification.  ident_dict will help with this by storing foo:bar where foo is a vertex from the original graph and bar is a vertex in the identified graph.  If vertices v1 and v2 are identified, then ident_dict[v1] == ident_dict[v2].  The labels of the vertices in the identified graph are the indices of the parts in partition.
        ident_dict = {
            y: x
            for x in range(len(partition)) for y in partition[x]
        }

        # To start checking the identified graph, we make the set of edges in the identified graph.
        new_edges = set()
        # For each edge in the old graph between v1 and v2, we add an edge between the parts y1 and y2 (where y1 is the part containing v1 and y2 is the part containing v2).  Since we are using a set, any duplicate edges get thrown out.
        for e in NCS.edges:
            a = ident_dict[e[0]]
            b = ident_dict[e[1]]
            if a > b:  # We enforce an ordering so there are no duplicates via reversal.  Given a partition, an edge (X,Y) in the identified graph is represented by an edge (x1,y1) where x1 in X and y1 in Y.  However, it might also be represented by an edge (y2,x2) where x2 in X and y2 in Y.  We don't want to represent an edge more than once in our set new_edges.
                a, b = b, a
            new_edges.add((a, b))
            # Note:  Our dictionary of forbidden identifications ensures that a != b.

        some_condition_failed = False  # If some_condition_failed switches to True, then we stop checking this partition and continue to the next partition.

        # Now that we have our edges, our first condition to check is if the identified graph is subcubic.  (Observation ?.1)
        # For each vertex in the identified graph, we count how many edges it is in.
        for x in range(len(partition)):
            degree_of_x = 0
            for e in new_edges:
                if x in e:
                    degree_of_x += 1
            if degree_of_x > 3:  # If we find a new vertex of degree more than 3, break immediately and continue to the next partition.
                some_condition_failed = True
                break

        if some_condition_failed:
            continue

        # To check the next conditions, we use the actual Graph object and get information about its specified faces.

        # First, get the graph.
        J = Graph(list(new_edges))
        # The graph we will check (J_prime) might have an additional edge to close the central face.
        J_prime = Graph(J)
        # We add the edge if the configuration is open and the spine ends were not identified.
        new_end_1, new_end_2 = ident_dict[NCS.faces[0][0]], ident_dict[
            NCS.faces[0][-1]]
        if NCS.is_open and new_end_1 != new_end_2:
            J_prime.add_edge(
                [new_end_1, new_end_2]
            )  # If the graph already has this edge, that is okay.  Then the edge is already serving the desired purpose.

        # Then, get the list of central/specified faces for this new graph.  We will need to check for possible identification of faces in the identification process, as we want the faces to be distinct when we check the next two conditions of Observation ? and get the open regions of the graph.
        # Note:  We are not checking that the faces have been identified with the same orientations of their boundaries (clockwise vs. counterclockwise).  Although this happens to be a necessary condition for a core subgraph, it is enforced by our planarity check later.
        new_faces = [
        ]  # Initialize new faces -- it will be the faces of J_prime.
        new_faces.append([ident_dict[x]
                          for x in NCS.faces[0]])  # Start with central face.
        central_face = new_faces[
            0]  # It will help readability to refer to 'central_face' instead of 'new_faces[0]'.
        # If the central face length is unspecified and the spine ends were identified, then we need to remove one of the spine ends to avoid duplicate vertices in the face list.
        if NCS.is_open and new_end_1 == new_end_2:
            central_face.pop()
        # Now we deal with the rest of the faces.
        for face in NCS.faces[1:]:
            new_face = [ident_dict[x]
                        for x in face]  # Convert to new vertex labels.
            for prev_face in new_faces[
                    1:]:  # We start comparison at index 1 to avoid comparing with the central face.  No specified face can be identified with the central face because the resulting face would contain a cut edge (the shared edge between the two faces in the natural core subgraph).  Since the face must also have a length at most 9, this violates Lemma ?.
                if set(new_face) == set(
                        prev_face
                ):  # If the set of vertices of the current new face is the same set of vertices as a previous face, then these faces must have been identified (because the graph is not a cycle).
                    break
            else:
                new_faces.append(new_face)

        # Now we can check to see if the identified graph has any trapped 2-vertices.  (Observation ?.3 -- we check this before Observation ?.2 because it is easier)
        for v in J_prime.vertices():
            if J_prime.degree(v) != 2:
                continue
            # The 2-vertex v is "trapped" if it appears more than once on the boundary walks of the fixed faces of J_prime.
            boundary_count_for_v = 0
            for face in new_faces:
                boundary_count_for_v += face.count(v)
            if boundary_count_for_v > 1:
                some_condition_failed = True
                break

        if some_condition_failed:
            continue

        # Now we begin checking Observation ?.2.  The first thing we check is that the spine does not wrap on itself -- Observation ?.2 says that the spinal path should be a boundary walk of a face in J_prime.  A boundary walk does not repeat an edge in the same direction.
        # (We need to check this only when the spine was open to begin with.)
        if NCS.is_open:
            for i, j in combinations(range(len(central_face) - 1), 2):
                if central_face[i] == central_face[j] and central_face[
                        i + 1] == central_face[j + 1]:
                    some_condition_failed = True
                    break
            if some_condition_failed:
                continue

        # Now we finish checking Observation ?.2.  For each fixed face of J_prime, we add a new hub vertex to be a representative for the face.  Then for each edge on the boundary of the face, another vertex is added which is connected to both the endpoints of the edge and to the midpoint of the edge and to the hub vertex representing the face.  In other words, we glue a wheel-like structure into each face in a particular way along the face boundary.  Then, we will check the resulting graph for planarity.

        i = J_prime.order()  # i will index the label of each new vertex.

        # First, we subdivide each edge so that we can connect some of our new vertices to the midpoints of our original edges.
        edge_dict = {
        }  # edge_dict will keep track of the labels of the midpoints of our original edges.
        for edge in J_prime.edges(
        ):  # The Graph edges iterator does not add edges part-way through the for loop.  Even though the edges of J_prime are changing over the course of the for loop, the objects in the iterator remain only those originally in J_prime when the for loop first started.
            J_prime.subdivide_edge((edge[0], edge[1]), 1)
            edge_dict[(edge[0], edge[1])] = i
            edge_dict[(
                edge[1], edge[0]
            )] = i  # Keep track of both orientations of the edge -- we do not know which orientations we will need.
            i += 1

        for face in new_faces:
            face_hub_vertex = i  # face_hub_vertex is the hub of the wheel-like structure being embedded on the face.
            i += 1
            J_prime.add_vertex(face_hub_vertex)
            for j in range(len(face)):
                edge_hub_vertex = i  # For each edge on the face, edge_hub_vertex is the vertex which is adjacent to face_hub_vertex and also to both endpoints and midpoint of the edge.
                i += 1
                J_prime.add_vertex(edge_hub_vertex)
                J_prime.add_edge(
                    (edge_hub_vertex, face[j - 1])
                )  # First endpoint.  Still works when j==0 due to Python's indexing.
                J_prime.add_edge(
                    (edge_hub_vertex, face[j]))  # Second endpoint.
                J_prime.add_edge(
                    (edge_hub_vertex, edge_dict[(face[j - 1],
                                                 face[j])]))  # Midpoint.
                J_prime.add_edge((edge_hub_vertex, face_hub_vertex))

        # Finally, check for planarity.
        if J_prime.is_planar():
            # If we made it this far, then all conditions were passed!
            countCS += 1
            # Print info and yield J (not J_prime).
            print
            print "      >>> " + "Core Subgraph #%d" % (
                countCS), "(Partition #%d)" % (count)
            print " " * 10 + "Partition: %s" % (str(partition))
            # We print the edges ten to each line.
            ed = [e[:2] for e in J.edges()]
            ed_str_li = []
            rows = len(ed) / 10
            for x in range(rows):
                ed_str_li.append(str(ed[10 * x:10 * (x + 1)])[1:-1] + ",")
            ed_str_li.append(str(ed[10 * rows:])[1:-1])
            print " " * 10 + "Edges: %s" % (ed_str_li[0])
            for s in ed_str_li[1:]:
                print " " * 17 + s
            yield J, new_faces

    print "Total #partitions:   ", count
def geometric_basis(G, E, p):
    r"""
    Return a geometric basis, based on a vertex.

    INPUT:

    - ``G`` -- the graph of the bounded regions of a Voronoi Diagram

    - ``E`` -- the subgraph of ``G`` formed by the edges that touch an unbounded
    region

    - ``p`` -- a vertex of ``E``

    OUTPUT: A geometric basis. It is formed by a list of sequences of paths.
    Each path is a list of vertices, that form a closed path in `G`, based at
    `p`, that goes to a region, surrounds it, and comes back by the same path it
    came. The concatenation of all these paths is equivalent to `E`.

    EXAMPLES::

        sage: from sage.schemes.curves.zariski_vankampen import geometric_basis
        sage: points = [(-3,0),(3,0),(0,3),(0,-3)]+ [(0,0),(0,-1),(0,1),(1,0),(-1,0)]
        sage: V = VoronoiDiagram(points)
        sage: G = Graph()
        sage: for reg  in V.regions().values():
        ....:     G = G.union(reg.vertex_graph())
        ....:
        sage: E = Graph()
        sage: for reg  in V.regions().values():
        ....:     if reg.rays() or reg.lines():
        ....:         E  = E.union(reg.vertex_graph())
        ....:
        sage: p = E.vertices()[0]
        sage: geometric_basis(G, E, p)
        [[A vertex at (-2, -2),
          A vertex at (2, -2),
          A vertex at (2, 2),
          A vertex at (1/2, 1/2),
          A vertex at (1/2, -1/2),
          A vertex at (2, -2),
          A vertex at (-2, -2)],
         [A vertex at (-2, -2),
          A vertex at (2, -2),
          A vertex at (1/2, -1/2),
          A vertex at (1/2, 1/2),
          A vertex at (-1/2, 1/2),
          A vertex at (-1/2, -1/2),
          A vertex at (1/2, -1/2),
          A vertex at (2, -2),
          A vertex at (-2, -2)],
         [A vertex at (-2, -2),
          A vertex at (2, -2),
          A vertex at (1/2, -1/2),
          A vertex at (-1/2, -1/2),
          A vertex at (-2, -2)],
         [A vertex at (-2, -2),
          A vertex at (-1/2, -1/2),
          A vertex at (-1/2, 1/2),
          A vertex at (1/2, 1/2),
          A vertex at (2, 2),
          A vertex at (-2, 2),
          A vertex at (-1/2, 1/2),
          A vertex at (-1/2, -1/2),
          A vertex at (-2, -2)],
         [A vertex at (-2, -2),
          A vertex at (-1/2, -1/2),
          A vertex at (-1/2, 1/2),
          A vertex at (-2, 2),
          A vertex at (-2, -2)]]

    """
    EC = [v[0] for v in orient_circuit(E.eulerian_circuit())]
    i = EC.index(p)
    EC = EC[
        i:] + EC[:i +
                 1]  # A counterclockwise eulerian circuit on the boundary, based at p
    if len(G.edges()) == len(E.edges()):
        if E.is_cycle():
            return [EC]
    I = Graph()
    for e in G.edges():
        if not E.has_edge(e):
            I.add_edge(e)  # interior graph
    # treat the case where I is empty
    if not I.vertices():
        for v in E.vertices():
            if len(E.neighbors(v)) > 2:
                I.add_vertex(v)

    for i in range(len(EC)):  # q and r are the points we will cut through

        if EC[i] in I.vertices():
            q = EC[i]
            connecting_path = EC[:i]
            break
        elif EC[-i] in I.vertices():
            q = EC[-i]
            connecting_path = list(reversed(EC[-i:]))
            break
    distancequotients = [
        (E.distance(q, v)**2 / I.distance(q, v), v) for v in E.vertices()
        if v in I.connected_component_containing_vertex(q) and not v == q
    ]
    r = max(distancequotients)[1]
    cutpath = I.shortest_path(q, r)
    Gcut = copy(G)
    Ecut = copy(E)
    Ecut.delete_vertices([q, r])
    Gcut.delete_vertices(cutpath)
    # I think this cannot happen, but just in case, we check it to raise
    # an error instead of giving a wrong answer
    if Gcut.connected_components_number() != 2:
        raise ValueError("unable to compute a correct path")
    G1, G2 = Gcut.connected_components_subgraphs()

    for v in cutpath:
        neighs = G.neighbors(v)
        for n in neighs:
            if n in G1.vertices() + cutpath:
                G1.add_edge(v, n, None)
            if n in G2.vertices() + cutpath:
                G2.add_edge(v, n, None)

    if EC[EC.index(q) + 1] in G2.vertices():
        G1, G2 = G2, G1

    E1, E2 = Ecut.connected_components_subgraphs()
    if EC[EC.index(q) + 1] in E2.vertices():
        E1, E2 = E2, E1

    for i in range(len(cutpath) - 1):
        E1.add_edge(cutpath[i], cutpath[i + 1], None)
        E2.add_edge(cutpath[i], cutpath[i + 1], None)

    for v in [q, r]:
        for n in E.neighbors(v):
            if n in E1.vertices():
                E1.add_edge(v, n, None)
            if n in E2.vertices():
                E2.add_edge(v, n, None)

    gb1 = geometric_basis(G1, E1, q)
    gb2 = geometric_basis(G2, E2, q)

    resul = [
        connecting_path + path + list(reversed(connecting_path))
        for path in gb1 + gb2
    ]
    for r in resul:
        i = 0
        while i < len(r) - 2:
            if r[i] == r[i + 2]:
                r.pop(i)
                r.pop(i)
                if i > 0:
                    i -= 1
            else:
                i += 1
    return resul
def random_cycle_paths(max_cycle_len,
                       max_path_len,
                       max_vertices,
                       p=0,
                       min_cycle_len=1):
    '''Funkcja losująca grafy nieskierowane o dokładnie jedym cyklu, z którego
    wychodzą ścieżki dwóch typów. Pierwszym z nich jest typ "zewnętrzny", czyli
    ścieżki, które zaczynają się w jednym z wierzchołków cyklu i których
    pozostałe wierzchołki są z cyklem rozłączne. Drugi typ jest "wewnętrzny",
    do którego należą ścieżki o dwóch wierzchołkach końcowych należących do
    cyklu lub innej ścieżki wewnętrznej. Wszystkie ścieżki wewnętrzne są
    losowane tak, żeby graf był planarny pod warukiem, że rysowanie ścieżek
    wewnętrznych ograniczamy do wnętrza cyklu.

    :param min_cycle_len: Int
        Minimalna dozwolona długość cyklu.
    :param max_cycle_len: Int
        Największa dozwolona długość cyklu. Jeżeli długość wylosowanego cyklu
        będzie równa `1`, to wygenerowany zostanie wierzchołek, a jeżeli będzie
        równa `2`, to wygenerowana zostanie krawędź.
    :param max_path_len: Int
        Największa dozwolona długość ścieżki wychodzącej z cyklu.
    :param p: Float
        Określa, z jakim prawdopodobieństwem do grafu zostanie dodana kolejna
        ścieżka zewnętrzna. Z prawdopodobieństwem `1-p` zostanie dodana krawędź
        wewnętrzna.
    :param max_vertices: Int
        Największa dozwolona liczba krawędzi.
    :return: tuple
        Para składająca się z opisanego grafu skierowanego, oraz listy
        wierzchołków składającej się z wierzchołków cyklu oraz ścieżek
        "zewnętrznych".
    '''
    if p < 0 or p > 1:
        raise ValueError("Niepoprawna wartość prawdopodobieństwa. `p` musi "
                         "należeć do przedziału [0, 1]")
    if min_cycle_len < 1:
        raise ValueError("Minimalna długość cyklu nie może być mniejsza od 1.")
    if min_cycle_len > max_cycle_len:
        raise ValueError("Minimalna długość cyklu musi być mniejsza lub równa "
                         "maksymalnej.")
    if min_cycle_len < 3 and p < 1:
        warnings.warn("Minimalna długość cyklu pozwala na stworzenie cykli "
                      "bez wnętrza, a wartość `p` dopuszcza istnienie ścieżek "
                      "wewnętrznych. W przypadku wylosowania krótkiego cyklu, "
                      "wszystkie ścieżki będą zewnętrzne.")
    G = Graph()
    cycle_len = np.random.randint(min_cycle_len, max_cycle_len + 1)
    if cycle_len == 1:
        p = 1
        G.add_vertex(0)
        outside_vertices = [0]
    elif cycle_len == 2:
        p = 1
        G.add_edge((0, 1))
        outside_vertices = [0, 1]
    else:
        G.add_cycle(list(range(0, cycle_len)))
        outside_vertices = list(range(0, cycle_len))
    n = cycle_len
    got_max_vertices = n >= max_vertices
    cycle_partitions = [list(range(0, n))]
    for i in range(cycle_len):
        if got_max_vertices:
            break
        n_paths = np.random.poisson(1)
        path_lengths = np.random.poisson(lam=int(max_path_len / 2),
                                         size=n_paths)
        for path_length in path_lengths:
            if n + path_length > max_vertices:
                path_length = max_vertices - n
                got_max_vertices = True
            if path_length == 0:
                if got_max_vertices:
                    break
                else:
                    continue
            if np.random.rand(1) > p:
                available_parts = [tab for tab in cycle_partitions if i in tab]
                cycle_part = \
                    available_parts[np.random.randint(0, len(available_parts))]
                j = i
                while j == i:
                    j = np.random.choice(cycle_part)
                # split
                k = 0
                parts = [[], []]
                part_id = 0
                new_path = list(range(n, n + path_length - 1))
                for k in cycle_part:
                    parts[part_id].append(k)
                    if k in [i, j]:
                        if k == i:
                            parts[part_id] += new_path
                        else:
                            parts[part_id] += new_path[::-1]
                        part_id = 1 - part_id
                        parts[part_id].append(k)
                cycle_partitions.remove(cycle_part)
                cycle_partitions.append(parts[0])
                cycle_partitions.append(parts[1])
                G.add_path([i] + new_path + [j])
            else:
                G.add_path([i] + list(range(n, n + path_length)))
                outside_vertices += list(range(n, n + path_length))
            n += path_length
            if got_max_vertices:
                break
    return G, outside_vertices