def sqrt_poly(g): if g.degree() == 0: return sqrt(g[0]) d = GCD(g, g.derivative(g.parent().gen())) sg = g // d return sg * sqrt_poly(g // sg**2)
def Eval0(cur,fra): """ perform the evaluation at the origin on the curve cur=0 of the rational function fra, where fra is the ordered list fra=[numerator,denominator] """ x, y = cur.parent().gens() phi,eta = PrePol(cur) gcd = GCD([cur] + fra) if ZeroAtOrigin(gcd): raise ValueError, "the numerator and denominator vanish identically" fr0 = [ff // gcd for ff in fra] while True: preva = [nn(0,0) for nn in fr0] if preva != [0,0]: return preva fr0 = [UnaSe(eta,phi,ff) for ff in fr0] gcd = GCD(fr0) fr0= [ff // gcd for ff in fr0]
def validate_character(level, character): """Assumes level is a positive integer N, checks that 0<character<=N and gcd(character,N)=1. Returns None if OK, else a suitable error message. """ #print "validate_character(%s,%s)" % (level, character) if not isinstance(character,int): return "The character number should be an integer. You gave: %s" % character from sage.all import GCD if character <= 0 or character > level or GCD(level,character)!=1: return "The character number should be a positive integer less than or equal to and coprime to the level %s. You gave: %s" % (level, character) return 0
def field_intersection_list(defining_polynomials): # try to just use discriminants of the first 1000 polynomials D = 0 for f in defining_polynomials[:1000]: D = GCD(D, f.discriminant()) if D == 1: return f.parent().gen() L = NumberField(defining_polynomials[0], 'a') for f in defining_polynomials: K = NumberField(f, 'b') # the 1st argument should be the 'smaller' field L = NumberField(field_intersection(L, K), 'a') if L.degree() == 1: return f.parent().gen() else: return polredabs(L.absolute_polynomial())
def make_E(self): coeffs = self.ainvs # list of 5 lists of d strings self.ainvs = [self.field.parse_NFelt(x) for x in coeffs] self.latex_ainvs = web_latex(self.ainvs) from sage.schemes.elliptic_curves.all import EllipticCurve self.E = E = EllipticCurve(self.ainvs) self.equn = web_latex(E) self.numb = str(self.number) # Conductor, discriminant, j-invariant N = E.conductor() self.cond = web_latex(N) self.cond_norm = web_latex(N.norm()) if N.norm() == 1: # since the factorization of (1) displays as "1" self.fact_cond = self.cond else: self.fact_cond = web_latex_ideal_fact(N.factor()) self.fact_cond_norm = web_latex(N.norm().factor()) D = self.field.K().ideal(E.discriminant()) self.disc = web_latex(D) self.disc_norm = web_latex(D.norm()) if D.norm() == 1: # since the factorization of (1) displays as "1" self.fact_disc = self.disc else: self.fact_disc = web_latex_ideal_fact(D.factor()) self.fact_disc_norm = web_latex(D.norm().factor()) # Minimal model? # # All curves in the database should be given # by models which are globally minimal if possible, else # minimal at all but one prime. But we do not rely on this # here, and the display should be correct if either (1) there # exists a global minimal model but this model is not; or (2) # this model is non-minimal at more than one prime. # self.non_min_primes = non_minimal_primes(E) self.is_minimal = (len(self.non_min_primes) == 0) self.has_minimal_model = True if not self.is_minimal: self.non_min_prime = ','.join( [web_latex(P) for P in self.non_min_primes]) self.has_minimal_model = has_global_minimal_model(E) if not self.is_minimal: Dmin = minimal_discriminant_ideal(E) self.mindisc = web_latex(Dmin) self.mindisc_norm = web_latex(Dmin.norm()) if Dmin.norm( ) == 1: # since the factorization of (1) displays as "1" self.fact_mindisc = self.mindisc else: self.fact_mindisc = web_latex_ideal_fact(Dmin.factor()) self.fact_mindisc_norm = web_latex(Dmin.norm().factor()) j = E.j_invariant() if j: d = j.denominator() n = d * j # numerator exists for quadratic fields only! g = GCD(list(n)) n1 = n / g self.j = web_latex(n1) if d != 1: if n1 > 1: #self.j = "("+self.j+")\(/\)"+web_latex(d) self.j = web_latex(r"\frac{%s}{%s}" % (self.j, d)) else: self.j = web_latex(d) if g > 1: if n1 > 1: self.j = web_latex(g) + self.j else: self.j = web_latex(g) self.j = web_latex(j) self.fact_j = None if j.is_zero(): self.fact_j = web_latex(j) else: try: self.fact_j = web_latex(j.factor()) except (ArithmeticError, ValueError): # if not all prime ideal factors principal pass # CM and End(E) self.cm_bool = "no" self.End = "\(\Z\)" if self.cm: self.cm_bool = "yes (\(%s\))" % self.cm if self.cm % 4 == 0: d4 = ZZ(self.cm) // 4 self.End = "\(\Z[\sqrt{%s}]\)" % (d4) else: self.End = "\(\Z[(1+\sqrt{%s})/2]\)" % self.cm # Q-curve / Base change self.qc = "no" try: if self.q_curve: self.qc = "yes" except AttributeError: # in case the db entry does not have this field set pass # Torsion self.ntors = web_latex(self.torsion_order) self.tr = len(self.torsion_structure) if self.tr == 0: self.tor_struct_pretty = "Trivial" if self.tr == 1: self.tor_struct_pretty = "\(\Z/%s\Z\)" % self.torsion_structure[0] if self.tr == 2: self.tor_struct_pretty = r"\(\Z/%s\Z\times\Z/%s\Z\)" % tuple( self.torsion_structure) torsion_gens = [ E([self.field.parse_NFelt(x) for x in P]) for P in self.torsion_gens ] self.torsion_gens = ",".join([web_latex(P) for P in torsion_gens]) # Rank etc try: self.rk = web_latex(self.rank) except AttributeError: self.rk = "not recorded" # if rank in self: # self.r = web_latex(self.rank) # Local data self.local_data = [] for p in N.prime_factors(): self.local_info = E.local_data(p, algorithm="generic") self.local_data.append({ 'p': web_latex(p), 'norm': web_latex(p.norm().factor()), 'tamagawa_number': self.local_info.tamagawa_number(), 'kodaira_symbol': web_latex(self.local_info.kodaira_symbol()).replace('$', ''), 'reduction_type': self.local_info.bad_reduction_type(), 'ord_den_j': max(0, E.j_invariant().valuation(p)), 'ord_mindisc': self.local_info.discriminant_valuation() }) # URLs of self and related objects: self.urls = {} self.urls['curve'] = url_for(".show_ecnf", nf=self.field_label, conductor_label=self.conductor_label, class_label=self.iso_label, number=self.number) self.urls['class'] = url_for(".show_ecnf_isoclass", nf=self.field_label, conductor_label=self.conductor_label, class_label=self.iso_label) self.urls['conductor'] = url_for(".show_ecnf_conductor", nf=self.field_label, conductor_label=self.conductor_label) self.urls['field'] = url_for(".show_ecnf1", nf=self.field_label) if self.field.is_real_quadratic(): self.hmf_label = "-".join( [self.field.label, self.conductor_label, self.iso_label]) self.urls['hmf'] = url_for('hmf.render_hmf_webpage', field_label=self.field.label, label=self.hmf_label) if self.field.is_imag_quadratic(): self.bmf_label = "-".join( [self.field.label, self.conductor_label, self.iso_label]) self.friends = [] self.friends += [('Isogeny class ' + self.short_class_label, self.urls['class'])] if self.field.is_real_quadratic(): self.friends += [('Hilbert Modular Form ' + self.hmf_label, self.urls['hmf'])] if self.field.is_imag_quadratic(): self.friends += [ ('Bianchi Modular Form %s not yet available' % self.bmf_label, '') ] self.properties = [('Base field', self.field.field_pretty()), ('Label', self.label)] # Plot n1 = len(E.base_field().embeddings(RR)) if (n1): self.plot = encode_plot(EC_nf_plot(E, self.field.generator_name())) self.plot_link = '<img src="%s" width="200" height="150"/>' % self.plot self.properties += [(None, self.plot_link)] self.properties += [('Conductor', self.cond), ('Conductor norm', self.cond_norm), ('j-invariant', self.j), ('CM', self.cm_bool)] if self.base_change: self.properties += [ ('base-change', 'yes: %s' % ','.join([str(lab) for lab in self.base_change])) ] else: self.base_change = [] # in case it was False instead of [] self.properties += [('Q-curve', self.qc)] self.properties += [ ('Torsion order', self.ntors), ('Rank', self.rk), ] for E0 in self.base_change: self.friends += [('Base-change of %s /\(\Q\)' % E0, url_for("ec.by_ec_label", label=E0))]
def make_E(self): coeffs = self.ainvs # list of 5 lists of d strings self.ainvs = [self.field.parse_NFelt(x) for x in coeffs] self.latex_ainvs = web_latex(self.ainvs) from sage.schemes.elliptic_curves.all import EllipticCurve self.E = E = EllipticCurve(self.ainvs) self.equn = web_latex(E) self.numb = str(self.number) # Conductor, discriminant, j-invariant N = E.conductor() self.cond = web_latex(N) self.cond_norm = web_latex(N.norm()) if N.norm() == 1: # since the factorization of (1) displays as "1" self.fact_cond = self.cond else: self.fact_cond = web_latex_ideal_fact(N.factor()) self.fact_cond_norm = web_latex(N.norm().factor()) D = self.field.K().ideal(E.discriminant()) self.disc = web_latex(D) self.disc_norm = web_latex(D.norm()) if D.norm() == 1: # since the factorization of (1) displays as "1" self.fact_disc = self.disc else: self.fact_disc = web_latex_ideal_fact(D.factor()) self.fact_disc_norm = web_latex(D.norm().factor()) # Minimal model? # # All curves in the database should be given # by models which are globally minimal if possible, else # minimal at all but one prime. But we do not rely on this # here, and the display should be correct if either (1) there # exists a global minimal model but this model is not; or (2) # this model is non-minimal at more than one prime. # self.non_min_primes = non_minimal_primes(E) self.is_minimal = (len(self.non_min_primes) == 0) self.has_minimal_model = True if not self.is_minimal: self.non_min_prime = ','.join( [web_latex(P) for P in self.non_min_primes]) self.has_minimal_model = has_global_minimal_model(E) if not self.is_minimal: Dmin = minimal_discriminant_ideal(E) self.mindisc = web_latex(Dmin) self.mindisc_norm = web_latex(Dmin.norm()) if Dmin.norm( ) == 1: # since the factorization of (1) displays as "1" self.fact_mindisc = self.mindisc else: self.fact_mindisc = web_latex_ideal_fact(Dmin.factor()) self.fact_mindisc_norm = web_latex(Dmin.norm().factor()) j = E.j_invariant() if j: d = j.denominator() n = d * j # numerator exists for quadratic fields only! g = GCD(list(n)) n1 = n / g self.j = web_latex(n1) if d != 1: if n1 > 1: # self.j = "("+self.j+")\(/\)"+web_latex(d) self.j = web_latex(r"\frac{%s}{%s}" % (self.j, d)) else: self.j = web_latex(d) if g > 1: if n1 > 1: self.j = web_latex(g) + self.j else: self.j = web_latex(g) self.j = web_latex(j) self.fact_j = None # See issue 1258: some j factorizations work bu take too long (e.g. EllipticCurve/6.6.371293.1/1.1/a/1) # If these are really wanted, they could be precomputed and stored in the db if j.is_zero(): self.fact_j = web_latex(j) else: if self.field.K().degree( ) < 3: #j.numerator_ideal().norm()<1000000000000: try: self.fact_j = web_latex(j.factor()) except (ArithmeticError, ValueError ): # if not all prime ideal factors principal pass # CM and End(E) self.cm_bool = "no" self.End = "\(\Z\)" if self.cm: self.cm_bool = "yes (\(%s\))" % self.cm if self.cm % 4 == 0: d4 = ZZ(self.cm) // 4 self.End = "\(\Z[\sqrt{%s}]\)" % (d4) else: self.End = "\(\Z[(1+\sqrt{%s})/2]\)" % self.cm # The line below will need to change once we have curves over non-quadratic fields # that contain the Hilbert class field of an imaginary quadratic field if self.signature == [0, 1] and ZZ( -self.abs_disc * self.cm).is_square(): self.ST = '<a href="%s">$%s$</a>' % (url_for( 'st.by_label', label='1.2.U(1)'), '\\mathrm{U}(1)') else: self.ST = '<a href="%s">$%s$</a>' % (url_for( 'st.by_label', label='1.2.N(U(1))'), 'N(\\mathrm{U}(1))') else: self.ST = '<a href="%s">$%s$</a>' % (url_for( 'st.by_label', label='1.2.SU(2)'), '\\mathrm{SU}(2)') # Q-curve / Base change self.qc = "no" try: if self.q_curve: self.qc = "yes" except AttributeError: # in case the db entry does not have this field set pass # Torsion self.ntors = web_latex(self.torsion_order) self.tr = len(self.torsion_structure) if self.tr == 0: self.tor_struct_pretty = "Trivial" if self.tr == 1: self.tor_struct_pretty = "\(\Z/%s\Z\)" % self.torsion_structure[0] if self.tr == 2: self.tor_struct_pretty = r"\(\Z/%s\Z\times\Z/%s\Z\)" % tuple( self.torsion_structure) torsion_gens = [ E([self.field.parse_NFelt(x) for x in P]) for P in self.torsion_gens ] self.torsion_gens = ",".join([web_latex(P) for P in torsion_gens]) # Rank or bounds try: self.rk = web_latex(self.rank) except AttributeError: self.rk = "?" try: self.rk_bnds = "%s...%s" % tuple(self.rank_bounds) except AttributeError: self.rank_bounds = [0, Infinity] self.rk_bnds = "not available" # Generators try: gens = [ E([self.field.parse_NFelt(x) for x in P]) for P in self.gens ] self.gens = ", ".join([web_latex(P) for P in gens]) if self.rk == "?": self.reg = "not available" else: if gens: self.reg = E.regulator_of_points(gens) else: self.reg = 1 # otherwise we only get 1.00000... except AttributeError: self.gens = "not available" self.reg = "not available" try: if self.rank == 0: self.reg = 1 except AttributeError: pass # Local data self.local_data = [] for p in N.prime_factors(): self.local_info = E.local_data(p, algorithm="generic") self.local_data.append({ 'p': web_latex(p), 'norm': web_latex(p.norm().factor()), 'tamagawa_number': self.local_info.tamagawa_number(), 'kodaira_symbol': web_latex(self.local_info.kodaira_symbol()).replace('$', ''), 'reduction_type': self.local_info.bad_reduction_type(), 'ord_den_j': max(0, -E.j_invariant().valuation(p)), 'ord_mindisc': self.local_info.discriminant_valuation(), 'ord_cond': self.local_info.conductor_valuation() }) # URLs of self and related objects: self.urls = {} # It's useful to be able to use this class out of context, when calling url_for will fail: try: self.urls['curve'] = url_for(".show_ecnf", nf=self.field_label, conductor_label=quote( self.conductor_label), class_label=self.iso_label, number=self.number) except RuntimeError: return self.urls['class'] = url_for(".show_ecnf_isoclass", nf=self.field_label, conductor_label=quote( self.conductor_label), class_label=self.iso_label) self.urls['conductor'] = url_for(".show_ecnf_conductor", nf=self.field_label, conductor_label=quote( self.conductor_label)) self.urls['field'] = url_for(".show_ecnf1", nf=self.field_label) sig = self.signature real_quadratic = sig == [2, 0] totally_real = sig[1] == 0 imag_quadratic = sig == [0, 1] if totally_real: self.hmf_label = "-".join( [self.field.label, self.conductor_label, self.iso_label]) self.urls['hmf'] = url_for('hmf.render_hmf_webpage', field_label=self.field.label, label=self.hmf_label) self.urls['Lfunction'] = url_for("l_functions.l_function_hmf_page", field=self.field_label, label=self.hmf_label, character='0', number='0') if imag_quadratic: self.bmf_label = "-".join( [self.field.label, self.conductor_label, self.iso_label]) self.friends = [] self.friends += [('Isogeny class ' + self.short_class_label, self.urls['class'])] self.friends += [('Twists', url_for('ecnf.index', field_label=self.field_label, jinv=self.jinv))] if totally_real: self.friends += [('Hilbert Modular Form ' + self.hmf_label, self.urls['hmf'])] self.friends += [('L-function', self.urls['Lfunction'])] if imag_quadratic: self.friends += [ ('Bianchi Modular Form %s not available' % self.bmf_label, '') ] self.properties = [('Base field', self.field.field_pretty()), ('Label', self.label)] # Plot if E.base_field().signature()[0]: self.plot = encode_plot(EC_nf_plot(E, self.field.generator_name())) self.plot_link = '<img src="%s" width="200" height="150"/>' % self.plot self.properties += [(None, self.plot_link)] self.properties += [ ('Conductor', self.cond), ('Conductor norm', self.cond_norm), # See issue #796 for why this is hidden # ('j-invariant', self.j), ('CM', self.cm_bool) ] if self.base_change: self.properties += [ ('base-change', 'yes: %s' % ','.join([str(lab) for lab in self.base_change])) ] else: self.base_change = [] # in case it was False instead of [] self.properties += [('Q-curve', self.qc)] r = self.rk if r == "?": r = self.rk_bnds self.properties += [ ('Torsion order', self.ntors), ('Rank', r), ] for E0 in self.base_change: self.friends += [('Base-change of %s /\(\Q\)' % E0, url_for("ec.by_ec_label", label=E0))] self._code = None # will be set if needed by get_code()
def prelabel(self): # this also sets: # - nu_* # - mu_* # and updates: # - gamma_factors def CCtuple(z): return (z.real(), z.imag().abs(), z.imag()) def spectral_str(x, conjugate=False): if conjugate: assert x <= 0 x = -x res = "c" elif x < 0: x = -x res = "m" else: res = "p" if x == 0: res += "0" else: res += "%.2f" % x return res GR, GC = self.gamma_factors GR = [elt + self.analytic_normalization for elt in GR] GC = [elt + self.analytic_normalization for elt in GC] b, e = self.conductor.perfect_power() if e == 1: conductor = b else: conductor = "{}e{}".format(b, e) beginning = "-".join(map(str, [self.degree, conductor, self.central_character])) GRcount = Counter(GR) GCcount = Counter(GC) # convert gamma_R to gamma_C for x in sorted(GRcount): shift = min(GRcount[x], GRcount[x+1]) if shift: # We store the real parts of nu doubled GCcount[x] += shift GRcount[x] -= shift GRcount[x] -= shift GR = sum([[m]*c for m, c in GRcount.items()], []) GC = sum([[m]*c for m, c in GCcount.items()], []) assert self.degree == len(GR) + 2*len(GC) GR.sort(key=CCtuple) GC.sort(key=CCtuple) self.mu_imag = [elt.imag() for elt in GR] self.nu_imag = [elt.imag() for elt in GC] # deal with real parts GR_real = [elt.real() for elt in GR] GC_real = [elt.real() for elt in GC] self.mu_real = [x.round() for x in GR_real] assert set(self.mu_real).issubset(set([0,1])) self.nu_real_doubled = [(2*x).round() for x in GC_real] GRcount = Counter(GR_real) GCcount = Counter(GC_real) ge = GCD(GCD(list(GRcount.values())), GCD(list(GCcount.values()))) if ge > 1: GR_real = sum(([k]*(v//ge) for k, v in GRcount.items()), []) GC_real = sum(([k]*(v//ge) for k, v in GCcount.items()), []) rs = ''.join(['r%d' % elt.real().round() for elt in GR_real]) cs = ''.join(['c%d' % (elt.real()*2).round() for elt in GC_real]) gammas = "-" + rs + cs if ge > 1: gammas += "e%d" % ge if self.algebraic: end = "-0" else: end = "-" for G in [GR, GC]: for i, elt in enumerate(G): conjugate = False if elt.imag() <= 0 and i < len(G) - 1 and elt.conjugate() == G[i + 1]: conjugate = True elif elt.imag() >= 0 and i > 0 and elt.conjugate() == G[i - 1]: # we already listed this one as a conjugate continue end += spectral_str(elt.imag(), conjugate=conjugate) # these are stored in algebraic normalization self.gamma_factors = [[elt - self.analytic_normalization for elt in G] for G in [GR, GC]] self.spectral_label = gammas + end return beginning + gammas + end
def field_intersection_matrix(matrix_defining_polynomials): r""" INPUT: - a matrix of polynomials defining numberfields (f_ij) OUTPUT: - returns a pair per column entry (Ak, Bk) after picking a row with a small intersection, WLOG i = 0 Let S_ij denote all the polynomials defining subfields of QQ[x]/fij Then Bk = S_0k \cap (\cap_{i > 0) \cup_j S_ij) If the list Bk can be obtained as list of subfields of one subfield then such subfield, otherwise Ak is None """ if len(matrix_defining_polynomials[0]) == 1: f = field_intersection_list( [elt[0] for elt in matrix_defining_polynomials]) return [[f, subfields_polynomials(f)]] # we would like to pick the row which has the smallest intersection # an easy to get something close to that,is by picking the one with # lowest GCD of the discriminants Di = None Dmin = None for i, row in enumerate(matrix_defining_polynomials): D = GCD([elt.discriminant() for elt in row]) if Dmin is None or D < Dmin: Dmin = D Di = i if Dmin == 1: break working_row = matrix_defining_polynomials[Di] subfields_matrix = [None] * len(matrix_defining_polynomials) output = [None] * len(working_row) for k, f in enumerate(working_row): Lsubfields = Set(subfields_polynomials(f)) for i, row in enumerate(matrix_defining_polynomials): if i != Di: if subfields_matrix[i] is None: U = Set([]) for g in row: Sg = Set(subfields_polynomials(g)) U = U.union(Sg) subfields_matrix[i] = U # try to discard some subfields Lsubfields = Lsubfields.intersection(subfields_matrix[i]) if Lsubfields.cardinality() == 1: # QQ is the only possible subfield break Lsubfields = list(Lsubfields) Lsubfields.sort() output[k] = [None, Lsubfields] # try to produce a single polynomial, if Lsubfields are all the subfields of a common subfield if len(Lsubfields) == 1: output[k][0] = f.parent().gen() else: if Lsubfields[-1].degree() != Lsubfields[-2].degree(): # there is one field of maximal degree, that might contain all the others K = NumberField(Lsubfields[-1], 'a') maxsubfields = [ polredabs(sK.absolute_polynomial()) for sK, _, _ in K.subfields() ] if maxsubfields == Lsubfields: output[k][0] = Lsubfields[-1] return output
def make_label(L, normalized=False): GR, GC = L['gamma_factors'] analytic_normalization = 0 if normalized else L['motivic_weight']/2 GR = [CDF(elt) + analytic_normalization for elt in GR] GC = [CDF(elt) + analytic_normalization for elt in GC] b, e = L['conductor'].perfect_power() if e == 1: conductor = b else: conductor = "{}e{}".format(b, e) beginning = "-".join(map(str, [L['degree'], conductor, L['central_character']])) GRcount = Counter(GR) GCcount = Counter(GC) # convert gamma_R to gamma_C zero = LmfdbRealLiteral(RR, '0') one = LmfdbRealLiteral(RR, '1') while GRcount[zero] > 0 and GRcount[one] > 0: GCcount[zero] += 1 GRcount[zero] -= 1 GRcount[one] -= 1 GR = sum([[m]*c for m, c in GRcount.items()], []) GC = sum([[m]*c for m, c in GCcount.items()], []) assert L['degree'] == len(GR) + 2*len(GC) GR.sort(key=CCtuple) GC.sort(key=CCtuple) L["mu_imag"] = [elt.imag() for elt in GR] L["nu_imag"] = [elt.imag() for elt in GC] # deal with real parts GR_real = [elt.real() for elt in GR] GC_real = [elt.real() for elt in GC] L["mu_real"] = [x.round() for x in GR_real] assert set(L["mu_real"]).issubset(set([0,1])) L["nu_real_doubled"] = [(2*x).round() for x in GC_real] GRcount = Counter(GR_real) GCcount = Counter(GC_real) ge = GCD(GCD(list(GRcount.values())), GCD(list(GCcount.values()))) if ge > 1: GR_real = sum(([k]*(v//ge) for k, v in GRcount.items()), []) GC_real = sum(([k]*(v//ge) for k, v in GCcount.items()), []) rs = ''.join(['r%d' % elt.real().round() for elt in GR_real]) cs = ''.join(['c%d' % (elt.real()*2).round() for elt in GC_real]) gammas = "-" + rs + cs if ge > 1: gammas += "e%d" % ge if L['algebraic']: end = "-0" else: end = "-" for G in [GR, GC]: for i, elt in enumerate(G): conjugate = False if elt.imag() <= 0 and i < len(G) - 1 and elt.conjugate() == G[i + 1]: conjugate = True elif elt.imag() >= 0 and i > 0 and elt.conjugate() == G[i - 1]: # we already listed this one as a conjugate continue end += spectral_str(elt.imag(), conjugate=conjugate) L["prelabel"] = beginning + gammas + end return tuple(GR), tuple(GC)