Path: blob/master/src/sage/geometry/fan_isomorphism.py
8815 views
"""1Find isomorphisms between fans.2"""345#*****************************************************************************6# Copyright (C) 2012 Volker Braun <[email protected]>7#8# Distributed under the terms of the GNU General Public License (GPL)9# as published by the Free Software Foundation; either version 2 of10# the License, or (at your option) any later version.11# http://www.gnu.org/licenses/12#*****************************************************************************1314from exceptions import Exception1516from sage.rings.all import ZZ17from sage.matrix.constructor import column_matrix, matrix18from sage.geometry.cone import Cone19202122class FanNotIsomorphicError(Exception):23"""24Exception to return if there is no fan isomorphism25"""26pass272829def fan_isomorphic_necessary_conditions(fan1, fan2):30"""31Check necessary (but not sufficient) conditions for the fans to be isomorphic.3233INPUT:3435- ``fan1``, ``fan2`` -- two fans.3637OUTPUT:3839Boolean. ``False`` if the two fans cannot be isomorphic. ``True``40if the two fans may be isomorphic.4142EXAMPLES::4344sage: fan1 = toric_varieties.P2().fan()45sage: fan2 = toric_varieties.dP8().fan()46sage: from sage.geometry.fan_isomorphism import fan_isomorphic_necessary_conditions47sage: fan_isomorphic_necessary_conditions(fan1, fan2)48False49"""50if fan1.lattice_dim() != fan2.lattice_dim():51return False52if fan1.dim() != fan2.dim():53return False54if fan1.nrays() != fan2.nrays():55return False56if fan1.ngenerating_cones() != fan2.ngenerating_cones():57return False58if fan1.is_complete() != fan2.is_complete():59return False60return True616263def fan_isomorphism_generator(fan1, fan2):64"""65Iterate over the isomorphisms from ``fan1`` to ``fan2``.6667ALGORITHM:6869The :meth:`sage.geometry.fan.Fan.vertex_graph` of the two fans is70compared. For each graph isomorphism, we attempt to lift it to an71actual isomorphism of fans.7273INPUT:7475- ``fan1``, ``fan2`` -- two fans.7677OUTPUT:7879Yields the fan isomorphisms as matrices acting from the right on80rays.8182EXAMPLES::8384sage: fan = toric_varieties.P2().fan()85sage: from sage.geometry.fan_isomorphism import fan_isomorphism_generator86sage: tuple( fan_isomorphism_generator(fan, fan) )87(88[1 0] [0 1] [ 1 0] [-1 -1] [ 0 1] [-1 -1]89[0 1], [1 0], [-1 -1], [ 1 0], [-1 -1], [ 0 1]90)9192sage: m1 = matrix([(1, 0), (0, -5), (-3, 4)])93sage: m2 = matrix([(3, 0), (1, 0), (-2, 1)])94sage: m1.elementary_divisors() == m2.elementary_divisors() == [1,1,0]95True96sage: fan1 = Fan([Cone([m1*vector([23, 14]), m1*vector([ 3,100])]),97... Cone([m1*vector([-1,-14]), m1*vector([-100, -5])])])98sage: fan2 = Fan([Cone([m2*vector([23, 14]), m2*vector([ 3,100])]),99... Cone([m2*vector([-1,-14]), m2*vector([-100, -5])])])100sage: fan_isomorphism_generator(fan1, fan2).next()101[18 1 -5]102[ 4 0 -1]103[ 5 0 -1]104105sage: m0 = identity_matrix(ZZ, 2)106sage: m1 = matrix([(1, 0), (0, -5), (-3, 4)])107sage: m2 = matrix([(3, 0), (1, 0), (-2, 1)])108sage: m1.elementary_divisors() == m2.elementary_divisors() == [1,1,0]109True110sage: fan0 = Fan([Cone([m0*vector([1,0]), m0*vector([1,1])]),111... Cone([m0*vector([1,1]), m0*vector([0,1])])])112sage: fan1 = Fan([Cone([m1*vector([1,0]), m1*vector([1,1])]),113... Cone([m1*vector([1,1]), m1*vector([0,1])])])114sage: fan2 = Fan([Cone([m2*vector([1,0]), m2*vector([1,1])]),115... Cone([m2*vector([1,1]), m2*vector([0,1])])])116sage: tuple(fan_isomorphism_generator(fan0, fan0))117(118[1 0] [0 1]119[0 1], [1 0]120)121sage: tuple(fan_isomorphism_generator(fan1, fan1))122(123[1 0 0] [ -3 -20 28]124[0 1 0] [ -1 -4 7]125[0 0 1], [ -1 -5 8]126)127sage: tuple(fan_isomorphism_generator(fan1, fan2))128(129[18 1 -5] [ 6 -3 7]130[ 4 0 -1] [ 1 -1 2]131[ 5 0 -1], [ 2 -1 2]132)133sage: tuple(fan_isomorphism_generator(fan2, fan1))134(135[ 0 -1 1] [ 0 -1 1]136[ 1 -7 2] [ 2 -2 -5]137[ 0 -5 4], [ 1 0 -3]138)139"""140if not fan_isomorphic_necessary_conditions(fan1, fan2):141return142143graph1 = fan1.vertex_graph()144graph2 = fan2.vertex_graph()145graph_iso = graph1.is_isomorphic(graph2, edge_labels=True, certify=True)146if not graph_iso[0]:147return148graph_iso = graph_iso[1]149150# Pick a basis of rays in fan1151max_cone = fan1(fan1.dim())[0]152fan1_pivot_rays = max_cone.rays()153fan1_basis = fan1_pivot_rays + fan1.virtual_rays() # A QQ-basis for N_1154fan1_pivot_cones = [ fan1.embed(Cone([r])) for r in fan1_pivot_rays ]155156# The fan2 cones as set(set(ray indices))157fan2_cones = frozenset(158frozenset(cone.ambient_ray_indices())159for cone in fan2.generating_cones() )160161# iterate over all graph isomorphisms graph1 -> graph2162for perm in graph2.automorphism_group(edge_labels=True):163# find a candidate m that maps fan1_basis to the image rays under the graph isomorphism164fan2_pivot_cones = [ perm(graph_iso[c]) for c in fan1_pivot_cones ]165fan2_pivot_rays = fan2.rays([ c.ambient_ray_indices()[0] for c in fan2_pivot_cones ])166fan2_basis = fan2_pivot_rays + fan2.virtual_rays()167try:168m = matrix(ZZ, fan1_basis).solve_right(matrix(ZZ, fan2_basis))169m = m.change_ring(ZZ)170except (ValueError, TypeError):171continue # no solution172173# check that the candidate m lifts the vertex graph homomorphism174graph_image_ray_indices = [ perm(graph_iso[c]).ambient_ray_indices()[0] for c in fan1(1) ]175try:176matrix_image_ray_indices = [ fan2.rays().index(r*m) for r in fan1.rays() ]177except ValueError:178continue179if graph_image_ray_indices != matrix_image_ray_indices:180continue181182# check that the candidate m maps generating cone to generating cone183image_cones = frozenset( # The image(fan1) cones as set(set(integers)184frozenset(graph_image_ray_indices[i]185for i in cone.ambient_ray_indices())186for cone in fan1.generating_cones() )187if image_cones == fan2_cones:188m.set_immutable()189yield m190191192def find_isomorphism(fan1, fan2, check=False):193"""194Find an isomorphism of the two fans.195196INPUT:197198- ``fan1``, ``fan2`` -- two fans.199200- ``check`` -- boolean (default: False). Passed to the fan201morphism constructor, see202:func:`~sage.geometry.fan_morphism.FanMorphism`.203204OUTPUT:205206A fan isomorphism. If the fans are not isomorphic, a207:class:`FanNotIsomorphicError` is raised.208209EXAMPLE::210211sage: rays = ((1, 1), (0, 1), (-1, -1), (3, 1))212sage: cones = [(0,1), (1,2), (2,3), (3,0)]213sage: fan1 = Fan(cones, rays)214215sage: m = matrix([[-2,3],[1,-1]])216sage: m.det() == -1217True218sage: fan2 = Fan(cones, [vector(r)*m for r in rays])219220sage: from sage.geometry.fan_isomorphism import find_isomorphism221sage: find_isomorphism(fan1, fan2, check=True)222Fan morphism defined by the matrix223[-2 3]224[ 1 -1]225Domain fan: Rational polyhedral fan in 2-d lattice N226Codomain fan: Rational polyhedral fan in 2-d lattice N227228sage: find_isomorphism(fan1, toric_varieties.P2().fan())229Traceback (most recent call last):230...231FanNotIsomorphicError232233sage: fan1 = Fan(cones=[[1,3,4,5],[0,1,2,3],[2,3,4],[0,1,5]],234... rays=[(-1,-1,0),(-1,-1,3),(-1,1,-1),(-1,3,-1),(0,2,-1),(1,-1,1)])235sage: fan2 = Fan(cones=[[0,2,3,5],[0,1,4,5],[0,1,2],[3,4,5]],236... rays=[(-1,-1,-1),(-1,-1,0),(-1,1,-1),(0,2,-1),(1,-1,1),(3,-1,-1)])237sage: fan1.is_isomorphic(fan2)238True239"""240generator = fan_isomorphism_generator(fan1, fan2)241try:242m = generator.next()243except StopIteration:244raise FanNotIsomorphicError245246from sage.geometry.fan_morphism import FanMorphism247return FanMorphism(m, domain_fan=fan1, codomain=fan2, check=check)248249250def fan_2d_cyclically_ordered_rays(fan):251"""252Return the rays of a 2-dimensional ``fan`` in cyclic order.253254INPUT:255256- ``fan`` -- a 2-dimensional fan.257258OUTPUT:259260A :class:`~sage.geometry.point_collection.PointCollection`261containing the rays in one particular cyclic order.262263EXAMPLES::264265sage: rays = ((1, 1), (-1, -1), (-1, 1), (1, -1))266sage: cones = [(0,2), (2,1), (1,3), (3,0)]267sage: fan = Fan(cones, rays)268sage: fan.rays()269N( 1, 1),270N(-1, -1),271N(-1, 1),272N( 1, -1)273in 2-d lattice N274sage: from sage.geometry.fan_isomorphism import fan_2d_cyclically_ordered_rays275sage: fan_2d_cyclically_ordered_rays(fan)276N(-1, -1),277N(-1, 1),278N( 1, 1),279N( 1, -1)280in 2-d lattice N281282TESTS::283284sage: fan = Fan(cones=[], rays=[], lattice=ZZ^2)285sage: from sage.geometry.fan_isomorphism import fan_2d_cyclically_ordered_rays286sage: fan_2d_cyclically_ordered_rays(fan)287Empty collection288in Ambient free module of rank 2 over the principal ideal domain Integer Ring289"""290assert fan.lattice_dim() == 2291import math292rays = [ (math.atan2(r[0],r[1]), r) for r in fan.rays() ]293rays = [ r[1] for r in sorted(rays) ]294from sage.geometry.point_collection import PointCollection295return PointCollection(rays, fan.lattice())296297298def fan_2d_echelon_forms(fan):299"""300Return echelon forms of all cyclically ordered ray matrices.301302Note that the echelon form of the ordered ray matrices are unique303up to different cyclic orderings.304305INPUT:306307- ``fan`` -- a fan.308309OUTPUT:310311A set of matrices. The set of all echelon forms for all different312cyclic orderings.313314EXAMPLES::315316sage: fan = toric_varieties.P2().fan()317sage: from sage.geometry.fan_isomorphism import fan_2d_echelon_forms318sage: fan_2d_echelon_forms(fan)319frozenset([[ 1 0 -1]320[ 0 1 -1]])321322sage: fan = toric_varieties.dP7().fan()323sage: list(fan_2d_echelon_forms(fan))324[325[ 1 0 -1 0 1] [ 1 0 -1 -1 0] [ 1 0 -1 -1 1] [ 1 0 -1 -1 0]326[ 0 1 0 -1 -1], [ 0 1 1 0 -1], [ 0 1 1 0 -1], [ 0 1 0 -1 -1],327<BLANKLINE>328[ 1 0 -1 0 1]329[ 0 1 1 -1 -1]330]331332TESTS::333334sage: rays = [(1, 1), (-1, -1), (-1, 1), (1, -1)]335sage: cones = [(0,2), (2,1), (1,3), (3,0)]336sage: fan1 = Fan(cones, rays)337sage: from sage.geometry.fan_isomorphism import fan_2d_echelon_form, fan_2d_echelon_forms338sage: echelon_forms = fan_2d_echelon_forms(fan1)339sage: S4 = CyclicPermutationGroup(4)340sage: rays.reverse()341sage: cones = [(3,1), (1,2), (2,0), (0,3)]342sage: for i in range(100):343... m = random_matrix(ZZ,2,2)344... if abs(det(m)) != 1: continue345... perm = S4.random_element()346... perm_cones = [ (perm(c[0]+1)-1, perm(c[1]+1)-1) for c in cones ]347... perm_rays = [ rays[perm(i+1)-1] for i in range(len(rays)) ]348... fan2 = Fan(perm_cones, rays=[m*vector(r) for r in perm_rays])349... assert fan_2d_echelon_form(fan2) in echelon_forms350"""351if fan.nrays() == 0:352return frozenset()353rays = list(fan_2d_cyclically_ordered_rays(fan))354echelon_forms = []355for i in range(2):356for j in range(len(rays)):357echelon_forms.append(column_matrix(rays).echelon_form())358first = rays.pop(0)359rays.append(first)360rays.reverse()361return frozenset(echelon_forms)362363364def fan_2d_echelon_form(fan):365"""366Return echelon form of a cyclically ordered ray matrix.367368INPUT:369370- ``fan`` -- a fan.371372OUTPUT:373374A matrix. The echelon form of the rays in one particular cyclic375order.376377EXAMPLES::378379sage: fan = toric_varieties.P2().fan()380sage: from sage.geometry.fan_isomorphism import fan_2d_echelon_form381sage: fan_2d_echelon_form(fan)382[ 1 0 -1]383[ 0 1 -1]384"""385ray_matrix = fan_2d_cyclically_ordered_rays(fan).matrix()386return ray_matrix.transpose().echelon_form()387388389390