@@ -6482,6 +6482,309 @@ def postcritical_set(self, check=True):
6482
6482
next_point = f (next_point )
6483
6483
return post_critical_list
6484
6484
6485
+ def is_chebyshev (self ):
6486
+ r"""
6487
+ Check if ``self`` is a Chebyshev polynomial.
6488
+
6489
+ OUTPUT: True if ``self`` is Chebyshev, False otherwise.
6490
+
6491
+ EXAMPLES::
6492
+
6493
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6494
+ sage: F = DynamicalSystem_projective([x^4, y^4])
6495
+ sage: F.is_chebyshev()
6496
+ False
6497
+
6498
+ ::
6499
+
6500
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6501
+ sage: F = DynamicalSystem_projective([x^2 + y^2, y^2])
6502
+ sage: F.is_chebyshev()
6503
+ False
6504
+
6505
+ ::
6506
+
6507
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6508
+ sage: F = DynamicalSystem_projective([2*x^2 - y^2, y^2])
6509
+ sage: F.is_chebyshev()
6510
+ True
6511
+
6512
+ ::
6513
+
6514
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6515
+ sage: F = DynamicalSystem_projective([x^3, 4*y^3 - 3*x^2*y])
6516
+ sage: F.is_chebyshev()
6517
+ True
6518
+
6519
+ ::
6520
+
6521
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6522
+ sage: F = DynamicalSystem_projective([2*x^2 - y^2, y^2])
6523
+ sage: L.<i> = CyclotomicField(4)
6524
+ sage: M = Matrix([[0,i],[-i,0]])
6525
+ sage: F.conjugate(M)
6526
+ Dynamical System of Projective Space of dimension 1 over Cyclotomic Field of order 4 and degree 2
6527
+ Defn: Defined on coordinates by sending (x : y) to
6528
+ ((-i)*x^2 : (-i)*x^2 + (2*i)*y^2)
6529
+ sage: F.is_chebyshev()
6530
+ True
6531
+
6532
+ REFERENCES:
6533
+
6534
+ - [Mil2006]_
6535
+ """
6536
+ # We need `F` to be defined over a number field for
6537
+ # the function `is_postcrtically_finite` to work
6538
+ if self .base_ring () not in NumberFields ():
6539
+ raise NotImplementedError ("Base ring must be a number field" )
6540
+
6541
+ if self .domain ().dimension () != 1 :
6542
+ return False
6543
+
6544
+ # All Chebyshev polynomials are postcritically finite
6545
+ if not self .is_postcritically_finite ():
6546
+ return False
6547
+
6548
+ # Get field of definition for critical points and change base field
6549
+ critical_field , phi = self .field_of_definition_critical (return_embedding = True )
6550
+ F_crit = self .change_ring (phi )
6551
+
6552
+ # Get the critical points and post-critical set
6553
+ crit_set = set (F_crit .critical_points ())
6554
+ post_crit_set = set ()
6555
+ images_needed = copy (crit_set )
6556
+ while len (images_needed ) != 0 :
6557
+ Q = images_needed .pop ()
6558
+ Q2 = F_crit (Q )
6559
+ if Q2 not in post_crit_set :
6560
+ post_crit_set .add (Q2 )
6561
+ images_needed .add (Q2 )
6562
+
6563
+ crit_fixed_pts = set ()
6564
+ for crit in crit_set :
6565
+ if F_crit .nth_iterate (crit , 1 ) == crit :
6566
+ crit_fixed_pts .add (crit )
6567
+
6568
+ # All Chebyshev maps have 3 post-critical values
6569
+ if (len (post_crit_set ) != 3 ) or (len (crit_fixed_pts ) != 1 ):
6570
+ return False
6571
+
6572
+ f = F_crit .dehomogenize (1 )[0 ]
6573
+ x = f .parent ().gen ()
6574
+ ram_points = {}
6575
+ for crit in crit_set :
6576
+ g = f
6577
+ new_crit = crit
6578
+
6579
+ # Check if critical point is infinity
6580
+ if crit [1 ] == 0 :
6581
+ g = g .subs (x = 1 / x )
6582
+ new_crit = F_crit .domain ()([0 , 1 ])
6583
+
6584
+ # Check if output is infinity
6585
+ if F_crit .nth_iterate (crit , 1 )[1 ] == 0 :
6586
+ g = 1 / g
6587
+
6588
+ new_crit = new_crit .dehomogenize (1 )[0 ]
6589
+ e = 1
6590
+ g = g .derivative (x )
6591
+
6592
+ while g (new_crit ) == 0 :
6593
+ e += 1
6594
+ g = g .derivative (x )
6595
+
6596
+ ram_points [crit ] = e
6597
+
6598
+ r = {}
6599
+
6600
+ # Set r value to 0 to represent infinity
6601
+ r [crit_fixed_pts .pop ()] = 0
6602
+
6603
+ # Get non-fixed tail points in the post-critical portrait
6604
+ crit_tails = crit_set .difference (post_crit_set )
6605
+ for crit in crit_tails :
6606
+ # Each critical tail point has r value 1
6607
+ r [crit ] = 1
6608
+ point = crit
6609
+
6610
+ # Assign r values to every point in the orbit of crit.
6611
+ # If we find a point in the orbit which already has an r value assigned,
6612
+ # check that we get a consistent r value.
6613
+ while F_crit .nth_iterate (point , 1 ) not in r .keys ():
6614
+ if point not in ram_points .keys ():
6615
+ r [F_crit .nth_iterate (point ,1 )] = r [point ]
6616
+ else :
6617
+ r [F_crit .nth_iterate (point ,1 )] = r [point ] * ram_points [point ]
6618
+
6619
+ point = F_crit .nth_iterate (point ,1 )
6620
+
6621
+ # Once we get here, the image of point has an assigned r value
6622
+ # We check that this value is consistent
6623
+ if point not in ram_points .keys ():
6624
+ if r [F_crit .nth_iterate (point ,1 )] != r [point ]:
6625
+ return False
6626
+ elif r [F_crit .nth_iterate (point ,1 )] != r [point ] * ram_points [point ]:
6627
+ return False
6628
+
6629
+ # The non-one r values must be one of the following in order for F to be Chebyshev
6630
+ r_vals = sorted ([val for val in r .values () if val != 1 ])
6631
+ return r_vals == [0 , 2 , 2 ]
6632
+
6633
+ def is_Lattes (self ):
6634
+ r"""
6635
+ Check if ``self`` is a Lattes map
6636
+
6637
+ OUTPUT: True if ``self`` is Lattes, False otherwise
6638
+
6639
+ EXAMPLES::
6640
+
6641
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6642
+ sage: F = DynamicalSystem_projective([x^3, y^3])
6643
+ sage: F.is_Lattes()
6644
+ False
6645
+
6646
+ ::
6647
+
6648
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6649
+ sage: F = DynamicalSystem_projective([x^2 - 2*y^2, y^2])
6650
+ sage: F.is_Lattes()
6651
+ False
6652
+
6653
+ ::
6654
+
6655
+ sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
6656
+ sage: F = DynamicalSystem_projective([x^2 + y^2 + z^2, y^2, z^2])
6657
+ sage: F.is_Lattes()
6658
+ False
6659
+
6660
+ ::
6661
+
6662
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6663
+ sage: F = DynamicalSystem_projective([(x + y)*(x - y)^3, y*(2*x + y)^3])
6664
+ sage: F.is_Lattes()
6665
+ True
6666
+
6667
+ ::
6668
+
6669
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6670
+ sage: F = DynamicalSystem_projective([(x + y)^4, 16*x*y*(x - y)^2])
6671
+ sage: F.is_Lattes()
6672
+ True
6673
+
6674
+ ::
6675
+
6676
+ sage: f = P.Lattes_map(EllipticCurve([0, 0, 0, 0, 2]),2)
6677
+ sage: f.is_Lattes()
6678
+ True
6679
+
6680
+ ::
6681
+
6682
+ sage: f = P.Lattes_map(EllipticCurve([0, 0, 0, 0, 2]), 2)
6683
+ sage: L.<i> = CyclotomicField(4)
6684
+ sage: M = Matrix([[i, 0], [0, -i]])
6685
+ sage: f.conjugate(M)
6686
+ Dynamical System of Projective Space of dimension 1 over Cyclotomic Field of order 4 and degree 2
6687
+ Defn: Defined on coordinates by sending (x : y) to
6688
+ ((-1/4*i)*x^4 + (-4*i)*x*y^3 : (-i)*x^3*y + (2*i)*y^4)
6689
+ sage: f.is_Lattes()
6690
+ True
6691
+
6692
+ REFERENCES:
6693
+
6694
+ - [Mil2006]_
6695
+ """
6696
+ # We need `f` to be defined over a number field for
6697
+ # the function `is_postcrtically_finite` to work
6698
+ if self .base_ring () not in NumberFields ():
6699
+ raise NotImplementedError ("Base ring must be a number field" )
6700
+
6701
+ if self .domain ().dimension () != 1 :
6702
+ return False
6703
+
6704
+ # All Lattes maps are postcritically finite
6705
+ if not self .is_postcritically_finite ():
6706
+ return False
6707
+
6708
+ # Get field of definition for critical points and change basefield
6709
+ critical_field , phi = self .field_of_definition_critical (return_embedding = True )
6710
+ F_crit = self .change_ring (phi )
6711
+
6712
+ # Get the critical points and post-critical set
6713
+ crit = F_crit .critical_points ()
6714
+ post_crit = set ()
6715
+ images_needed = copy (crit )
6716
+ while len (images_needed ) != 0 :
6717
+ Q = images_needed .pop ()
6718
+ Q2 = F_crit (Q )
6719
+ if Q2 not in post_crit :
6720
+ post_crit .add (Q2 )
6721
+ images_needed .append (Q2 )
6722
+
6723
+ (crit_set , post_crit_set ) = crit , list (post_crit )
6724
+
6725
+ # All Lattes maps have 3 or 4 post critical values
6726
+ if not len (post_crit_set ) in [3 , 4 ]:
6727
+ return False
6728
+
6729
+ f = F_crit .dehomogenize (1 )[0 ]
6730
+ x = f .parent ().gen ()
6731
+ ram_points = {}
6732
+ for crit in crit_set :
6733
+ g = f
6734
+ new_crit = crit
6735
+
6736
+ # Check if critical point is infinity
6737
+ if crit [1 ] == 0 :
6738
+ g = g .subs (x = 1 / x )
6739
+ new_crit = F_crit .domain ()([0 , 1 ])
6740
+
6741
+ # Check if output is infinity
6742
+ if F_crit .nth_iterate (crit , 1 )[1 ] == 0 :
6743
+ g = 1 / g
6744
+
6745
+ new_crit = new_crit .dehomogenize (1 )[0 ]
6746
+
6747
+ e = 1
6748
+ g = g .derivative (x )
6749
+ while g (new_crit ) == 0 :
6750
+ e += 1
6751
+ g = g .derivative (x )
6752
+ ram_points [crit ] = e
6753
+
6754
+ r = {}
6755
+
6756
+ # Get tail points in the post-critical portrait
6757
+ crit_tails = set (crit_set ).difference (set (post_crit_set ))
6758
+ for crit in crit_tails :
6759
+ # Each critical tail point has r value 1
6760
+ r [crit ] = 1
6761
+ point = crit
6762
+
6763
+ # Assign r values to every point in the orbit of crit
6764
+ # If we find a point in the orbit which already has an r value assigned,
6765
+ # check that we get a consistent r value
6766
+ while F_crit .nth_iterate (point , 1 ) not in r .keys ():
6767
+ if point not in ram_points .keys ():
6768
+ r [F_crit .nth_iterate (point , 1 )] = r [point ]
6769
+ else :
6770
+ r [F_crit .nth_iterate (point , 1 )] = r [point ] * ram_points [point ]
6771
+
6772
+ point = F_crit .nth_iterate (point ,1 )
6773
+
6774
+ # Once we get here the image of point has an assigned r value
6775
+ # We check that this value is consistent.
6776
+ if point not in ram_points .keys ():
6777
+ if r [F_crit .nth_iterate (point , 1 )] != r [point ]:
6778
+ return False
6779
+ else :
6780
+ if r [F_crit .nth_iterate (point , 1 )] != r [point ] * ram_points [point ]:
6781
+ return False
6782
+
6783
+ # The non-one r values must be one of the following in order for F to be Lattes
6784
+ r_lattes_cases = [[2 , 2 , 2 , 2 ], [3 , 3 , 3 ], [2 , 4 , 4 ], [2 , 3 , 6 ]]
6785
+ r_vals = sorted ([val for val in r .values () if val != 1 ])
6786
+ return r_vals in r_lattes_cases
6787
+
6485
6788
6486
6789
class DynamicalSystem_projective_field (DynamicalSystem_projective ,
6487
6790
SchemeMorphism_polynomial_projective_space_field ):
0 commit comments