def _polar_graph(m, q, g, intersection_size=None): r""" The helper function to build graphs `(D)U(m,q)` and `(D)Sp(m,q)` Building a graph on an orbit of a group `g` of `m\times m` matrices over `GF(q)` on the points (or subspaces of dimension ``m//2``) isotropic w.r.t. the form `F` left invariant by the group `g`. The only constraint is that the first ``m//2`` elements of the standard basis must generate a totally isotropic w.r.t. `F` subspace; this is the case with these groups coming from GAP; namely, `F` has the anti-diagonal all-1 matrix. INPUT: - ``m`` -- the dimension of the underlying vector space - ``q`` -- the size of the field - ``g`` -- the group acting - ``intersection_size`` -- if ``None``, build the graph on the isotropic points, with adjacency being orthogonality w.r.t. `F`. Otherwise, build the graph on the maximal totally isotropic subspaces, with adjacency specified by ``intersection_size`` being as given. TESTS:: sage: from sage.graphs.generators.classical_geometries import _polar_graph sage: _polar_graph(4, 4, libgap.GeneralUnitaryGroup(4, 2)) Graph on 45 vertices sage: _polar_graph(4, 4, libgap.GeneralUnitaryGroup(4, 2), intersection_size=1) Graph on 27 vertices """ from sage.libs.gap.libgap import libgap from itertools import combinations W = libgap.FullRowSpace(libgap.GF(q), m) # F_q^m B = libgap.Elements(libgap.Basis(W)) # the standard basis of W V = libgap.Orbit(g, B[0], libgap.OnLines) # orbit on isotropic points gp = libgap.Action(g, V, libgap.OnLines) # make a permutation group s = libgap.Subspace( W, [B[i] for i in range(m // 2)]) # a totally isotropic subspace # and the points there sp = [ libgap.Elements(libgap.Basis(x))[0] for x in libgap.Elements(s.Subspaces(1)) ] h = libgap.Set(map(lambda x: libgap.Position(V, x), sp)) # indices of the points in s L = libgap.Orbit(gp, h, libgap.OnSets) # orbit on these subspaces if intersection_size == None: G = Graph() for x in L: # every pair of points in the subspace is adjacent to each other in G G.add_edges(combinations(x, 2)) return G else: return Graph([ L, lambda i, j: libgap.Size(libgap.Intersection(i, j)) == intersection_size ], loops=False)
def NonisotropicUnitaryPolarGraph(m, q): r""" Returns the Graph `NU(m,q)`. Returns the graph on nonisotropic, with respect to a nondegenerate Hermitean form, points of the `(m-1)`-dimensional projective space over `F_q`, with points adjacent whenever they lie on a tangent (to the set of isotropic points) line. For more information, see Sect. 9.9 of [BH12]_ and series C14 in [Hu75]_. INPUT: - ``m,q`` (integers) -- `q` must be a prime power. EXAMPLES:: sage: g=graphs.NonisotropicUnitaryPolarGraph(5,2); g NU(5, 2): Graph on 176 vertices sage: g.is_strongly_regular(parameters=True) (176, 135, 102, 108) TESTS:: sage: graphs.NonisotropicUnitaryPolarGraph(4,2).is_strongly_regular(parameters=True) (40, 27, 18, 18) sage: graphs.NonisotropicUnitaryPolarGraph(4,3).is_strongly_regular(parameters=True) # long time (540, 224, 88, 96) sage: graphs.NonisotropicUnitaryPolarGraph(6,6) Traceback (most recent call last): ... ValueError: q must be a prime power REFERENCE: .. [Hu75] X. L. Hubaut. Strongly regular graphs. Disc. Math. 13(1975), pp 357--381. http://dx.doi.org/10.1016/0012-365X(75)90057-6 """ from sage.rings.arith import is_prime_power p, k = is_prime_power(q, get_data=True) if k == 0: raise ValueError('q must be a prime power') from sage.libs.gap.libgap import libgap from itertools import combinations F = libgap.GF(q**2) # F_{q^2} W = libgap.FullRowSpace(F, m) # F_{q^2}^m B = libgap.Elements(libgap.Basis(W)) # the standard basis of W if m % 2 != 0: point = B[(m - 1) / 2] else: if p == 2: point = B[m / 2] + F.PrimitiveRoot() * B[(m - 2) / 2] else: point = B[(m - 2) / 2] + B[m / 2] g = libgap.GeneralUnitaryGroup(m, q) V = libgap.Orbit(g, point, libgap.OnLines) # orbit on nonisotropic points gp = libgap.Action(g, V, libgap.OnLines) # make a permutation group s = libgap.Subspace(W, [point, point + B[0]]) # a tangent line on point # and the points there sp = [ libgap.Elements(libgap.Basis(x))[0] for x in libgap.Elements(s.Subspaces(1)) ] h = libgap.Set( map(lambda x: libgap.Position(V, x), libgap.Intersection(V, sp))) # indices L = libgap.Orbit(gp, h, libgap.OnSets) # orbit on the tangent lines G = Graph() for x in L: # every pair of points in the subspace is adjacent to each other in G G.add_edges(combinations(x, 2)) G.relabel() G.name("NU" + str((m, q))) return G
def _orthogonal_polar_graph(m, q, sign="+", point_type=[0]): r""" A helper function to build ``OrthogonalPolarGraph`` and ``NO2,3,5`` graphs. See see the `page of Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/srghub.html>`_. INPUT: - ``m,q`` (integers) -- `q` must be a prime power. - ``sign`` -- ``"+"`` or ``"-"`` if `m` is even, ``"+"`` (default) otherwise. - ``point_type`` -- a list of elements from `F_q` EXAMPLES: Petersen graph:: ` sage: from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph sage: g=_orthogonal_polar_graph(3,5,point_type=[2,3]) sage: g.is_strongly_regular(parameters=True) (10, 3, 0, 1) A locally Petersen graph (a.k.a. Doro graph, a.k.a. Hall graph):: sage: g=_orthogonal_polar_graph(4,5,'-',point_type=[2,3]) sage: g.is_distance_regular(parameters=True) ([10, 6, 4, None], [None, 1, 2, 5]) Various big and slow to build graphs: `NO^+(7,3)`:: sage: g=_orthogonal_polar_graph(7,3,point_type=[1]) # not tested (long time) sage: g.is_strongly_regular(parameters=True) # not tested (long time) (378, 117, 36, 36) `NO^-(7,3)`:: sage: g=_orthogonal_polar_graph(7,3,point_type=[-1]) # not tested (long time) sage: g.is_strongly_regular(parameters=True) # not tested (long time) (351, 126, 45, 45) `NO^+(6,3)`:: sage: g=_orthogonal_polar_graph(6,3,point_type=[1]) sage: g.is_strongly_regular(parameters=True) (117, 36, 15, 9) `NO^-(6,3)`:: sage: g=_orthogonal_polar_graph(6,3,'-',point_type=[1]) sage: g.is_strongly_regular(parameters=True) (126, 45, 12, 18) `NO^{-,\perp}(5,5)`:: sage: g=_orthogonal_polar_graph(5,5,point_type=[2,3]) # long time sage: g.is_strongly_regular(parameters=True) # long time (300, 65, 10, 15) `NO^{+,\perp}(5,5)`:: sage: g=_orthogonal_polar_graph(5,5,point_type=[1,-1]) # not tested (long time) sage: g.is_strongly_regular(parameters=True) # not tested (long time) (325, 60, 15, 10) TESTS:: sage: g=_orthogonal_polar_graph(5,3,point_type=[-1]) sage: g.is_strongly_regular(parameters=True) (45, 12, 3, 3) sage: g=_orthogonal_polar_graph(5,3,point_type=[1]) sage: g.is_strongly_regular(parameters=True) (36, 15, 6, 6) """ from sage.schemes.projective.projective_space import ProjectiveSpace from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector from sage.matrix.constructor import Matrix from sage.libs.gap.libgap import libgap from itertools import combinations if m % 2 == 0: if sign != "+" and sign != "-": raise ValueError("sign must be equal to either '-' or '+' when " "m is even") else: if sign != "" and sign != "+": raise ValueError("sign must be equal to either '' or '+' when " "m is odd") sign = "" e = {'+': 1, '-': -1, '': 0}[sign] M = Matrix( libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup( e, m, q))['matrix']) Fq = libgap.GF(q).sage() PG = map(vector, ProjectiveSpace(m - 1, Fq)) map(lambda x: x.set_immutable(), PG) def F(x): return x * M * x if q % 2 == 0: def P(x, y): return F(x - y) else: def P(x, y): return x * M * y + y * M * x V = [x for x in PG if F(x) in point_type] G = Graph([V, lambda x, y: P(x, y) == 0], loops=False) G.relabel() return G
def NonisotropicOrthogonalPolarGraph(m, q, sign="+", perp=None): r""" Returns the Graph `NO^{\epsilon,\perp}_{m}(q)` Let the vectorspace of dimension `m` over `F_q` be endowed with a nondegenerate quadratic form `F`, of type ``sign`` for `m` even. * `m` even: assume further that `q=2` or `3`. Returns the graph of the points (in the underlying projective space) `x` satisfying `F(x)=1`, with adjacency given by orthogonality w.r.t. `F`. Parameter ``perp`` is ignored. * `m` odd: if ``perp`` is not ``None``, then we assume that `q=5` and return the graph of the points `x` satisfying `F(x)=\pm 1` if ``sign="+"``, respectively `F(x) \in \{2,3\}` if ``sign="-"``, with adjacency given by orthogonality w.r.t. `F` (cf. Sect 7.D of [BvL84]_). Otherwise return the graph of nongenerate hyperplanes of type ``sign``, adjacent whenever the intersection is degenerate (cf. Sect. 7.C of [BvL84]_). Note that for `q=2` one will get a complete graph. For more information, see Sect. 9.9 of [BH12]_ and [BvL84]_. Note that the `page of Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/srghub.html>`_ uses different notation. INPUT: - ``m`` - integer, half the dimension of the underlying vectorspace - ``q`` - a power of a prime number, the size of the underlying field - ``sign`` -- ``"+"`` (default) or ``"-"``. EXAMPLES: `NO^-(4,2)` is isomorphic to Petersen graph:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(4,2,'-'); g NO^-(4, 2): Graph on 10 vertices sage: g.is_strongly_regular(parameters=True) (10, 3, 0, 1) `NO^-(6,2)` and `NO^+(6,2)`:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,2,'-') sage: g.is_strongly_regular(parameters=True) (36, 15, 6, 6) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,2,'+'); g NO^+(6, 2): Graph on 28 vertices sage: g.is_strongly_regular(parameters=True) (28, 15, 6, 10) `NO^+(8,2)`:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(8,2,'+') sage: g.is_strongly_regular(parameters=True) (120, 63, 30, 36) Wilbrink's graphs for `q=5`:: sage: graphs.NonisotropicOrthogonalPolarGraph(5,5,perp=1).is_strongly_regular(parameters=True) # long time (325, 60, 15, 10) sage: graphs.NonisotropicOrthogonalPolarGraph(5,5,'-',perp=1).is_strongly_regular(parameters=True) # long time (300, 65, 10, 15) Wilbrink's graphs:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,4,'+') sage: g.is_strongly_regular(parameters=True) (136, 75, 42, 40) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,4,'-') sage: g.is_strongly_regular(parameters=True) (120, 51, 18, 24) sage: g=graphs.NonisotropicOrthogonalPolarGraph(7,4,'+'); g # not tested (long time) NO^+(7, 4): Graph on 2080 vertices sage: g.is_strongly_regular(parameters=True) # not tested (long time) (2080, 1071, 558, 544) TESTS:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(4,2); g NO^+(4, 2): Graph on 6 vertices sage: graphs.NonisotropicOrthogonalPolarGraph(4,3,'-').is_strongly_regular(parameters=True) (15, 6, 1, 3) sage: g=graphs.NonisotropicOrthogonalPolarGraph(3,5,'-',perp=1); g NO^-,perp(3, 5): Graph on 10 vertices sage: g.is_strongly_regular(parameters=True) (10, 3, 0, 1) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,3,'+') # long time sage: g.is_strongly_regular(parameters=True) # long time (117, 36, 15, 9) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,3,'-'); g # long time NO^-(6, 3): Graph on 126 vertices sage: g.is_strongly_regular(parameters=True) # long time (126, 45, 12, 18) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,5,'-') # long time sage: g.is_strongly_regular(parameters=True) # long time (300, 104, 28, 40) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,5,'+') # long time sage: g.is_strongly_regular(parameters=True) # long time (325, 144, 68, 60) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,4,'+') Traceback (most recent call last): ... ValueError: for m even q must be 2 or 3 """ from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph from sage.rings.arith import is_prime_power p, k = is_prime_power(q, get_data=True) if k == 0: raise ValueError('q must be a prime power') dec = '' if m % 2 == 0: if q in [2, 3]: G = _orthogonal_polar_graph(m, q, sign=sign, point_type=[1]) else: raise ValueError("for m even q must be 2 or 3") elif not perp is None: if q == 5: G = _orthogonal_polar_graph(m, q, point_type=\ [-1,1] if sign=='+' else [2,3] if sign=='-' else []) dec = ",perp" else: raise ValueError("for perp not None q must be 5") else: if not sign in ['+', '-']: raise ValueError("sign must be '+' or '-'") from sage.libs.gap.libgap import libgap g0 = libgap.GeneralOrthogonalGroup(m, q) g = libgap.Group( libgap.List(g0.GeneratorsOfGroup(), libgap.TransposedMat)) F = libgap.GF(q) # F_q W = libgap.FullRowSpace(F, m) # F_q^m e = 1 if sign == '+' else -1 n = (m - 1) / 2 # we build (q^n(q^n+e)/2, (q^n-e)(q^(n-1)+e), 2(q^(2n-2)-1)+eq^(n-1)(q-1), # 2q^(n-1)(q^(n-1)+e))-srg # **use** v and k to select appropriate orbit and orbital nvert = (q**n) * (q**n + e) / 2 # v deg = (q**n - e) * (q**(n - 1) + e) # k S=map(lambda x: libgap.Elements(libgap.Basis(x))[0], \ libgap.Elements(libgap.Subspaces(W,1))) V = filter(lambda x: len(x) == nvert, libgap.Orbits(g, S, libgap.OnLines)) assert len(V) == 1 V = V[0] gp = libgap.Action(g, V, libgap.OnLines) # make a permutation group h = libgap.Stabilizer(gp, 1) Vh = filter(lambda x: len(x) == deg, libgap.Orbits(h, libgap.Orbit(gp, 1))) assert len(Vh) == 1 Vh = Vh[0][0] L = libgap.Orbit(gp, [1, Vh], libgap.OnSets) G = Graph() G.add_edges(L) G.name("NO^" + sign + dec + str((m, q))) return G
def AffineOrthogonalPolarGraph(d, q, sign="+"): r""" Returns the affine polar graph `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. Affine Polar graphs are built from a `d`-dimensional vector space over `F_q`, and a quadratic form which is hyperbolic, elliptic or parabolic according to the value of ``sign``. Note that `VO^+(d,q),VO^-(d,q)` are strongly regular graphs, while `VO(d,q)` is not. For more information on Affine Polar graphs, see `Affine Polar Graphs page of Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/VO.html>`_. INPUT: - ``d`` (integer) -- ``d`` must be even if ``sign != None``, and odd otherwise. - ``q`` (integer) -- a power of a prime number, as `F_q` must exist. - ``sign`` -- must be equal to ``"+"``, ``"-"``, or ``None`` to compute (respectively) `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. By default ``sign="+"``. .. NOTE:: The graph `VO^\epsilon(d,q)` is the graph induced by the non-neighbors of a vertex in an :meth:`Orthogonal Polar Graph <OrthogonalPolarGraph>` `O^\epsilon(d+2,q)`. EXAMPLES: The :meth:`Brouwer-Haemers graph <BrouwerHaemersGraph>` is isomorphic to `VO^-(4,3)`:: sage: g = graphs.AffineOrthogonalPolarGraph(4,3,"-") sage: g.is_isomorphic(graphs.BrouwerHaemersGraph()) True Some examples from `Brouwer's table or strongly regular graphs <http://www.win.tue.nl/~aeb/graphs/srg/srgtab.html>`_:: sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"-"); g Affine Polar Graph VO^-(6,2): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 27, 10, 12) sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"+"); g Affine Polar Graph VO^+(6,2): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 35, 18, 20) When ``sign is None``:: sage: g = graphs.AffineOrthogonalPolarGraph(5,2,None); g Affine Polar Graph VO^-(5,2): Graph on 32 vertices sage: g.is_strongly_regular(parameters=True) False sage: g.is_regular() True sage: g.is_vertex_transitive() True """ if sign in ["+", "-"]: s = 1 if sign == "+" else -1 if d % 2 == 1: raise ValueError("d must be even when sign!=None") else: if d % 2 == 0: raise ValueError("d must be odd when sign==None") s = 0 from sage.interfaces.gap import gap from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module import VectorSpace from sage.matrix.constructor import Matrix from sage.libs.gap.libgap import libgap from itertools import combinations M = Matrix( libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup( s, d, q))['matrix']) F = libgap.GF(q).sage() V = list(VectorSpace(F, d)) G = Graph() G.add_vertices([tuple(_) for _ in V]) for x, y in combinations(V, 2): if not (x - y) * M * (x - y): G.add_edge(tuple(x), tuple(y)) G.name("Affine Polar Graph VO^" + str('+' if s == 1 else '-') + "(" + str(d) + "," + str(q) + ")") G.relabel() return G