| 1 | """ |
|---|
| 2 | Scheme morphism |
|---|
| 3 | |
|---|
| 4 | AUTHORS: |
|---|
| 5 | -- David Kohel, William Stein |
|---|
| 6 | -- William Stein (2006-02-11): fixed bug where P(0,0,0) was allowed as a projective point. |
|---|
| 7 | """ |
|---|
| 8 | |
|---|
| 9 | #***************************************************************************** |
|---|
| 10 | # Copyright (C) 2006 David Kohel <kohel@maths.usyd.edu.au> |
|---|
| 11 | # Copyright (C) 2006 William Stein <wstein@gmail.com> |
|---|
| 12 | # Distributed under the terms of the GNU General Public License (GPL) |
|---|
| 13 | # http://www.gnu.org/licenses/ |
|---|
| 14 | #***************************************************************************** |
|---|
| 15 | |
|---|
| 16 | from sage.structure.element import AdditiveGroupElement, RingElement, Element |
|---|
| 17 | from sage.structure.sequence import Sequence |
|---|
| 18 | |
|---|
| 19 | from sage.categories.morphism import Morphism |
|---|
| 20 | from sage.categories.homset import Homset |
|---|
| 21 | |
|---|
| 22 | from sage.rings.all import is_RingHomomorphism, is_CommutativeRing, Integer |
|---|
| 23 | |
|---|
| 24 | from point import is_SchemeTopologicalPoint |
|---|
| 25 | |
|---|
| 26 | import scheme |
|---|
| 27 | |
|---|
| 28 | import spec |
|---|
| 29 | |
|---|
| 30 | def is_SchemeMorphism(f): |
|---|
| 31 | from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field # TODO: fix circular ref. |
|---|
| 32 | return isinstance(f, (SchemeMorphism, EllipticCurvePoint_field)); |
|---|
| 33 | |
|---|
| 34 | |
|---|
| 35 | class PyMorphism(Element): |
|---|
| 36 | # Double inheritance from both Morphism and AdditiveGroupElement seems to mess up the ModuleElement pyrex vtab, which is really bad! |
|---|
| 37 | def __init__(self, parent): |
|---|
| 38 | if not isinstance(parent, Homset): |
|---|
| 39 | raise TypeError, "parent (=%s) must be a Homspace"%parent |
|---|
| 40 | Element.__init__(self, parent) |
|---|
| 41 | self._domain = parent.domain() |
|---|
| 42 | self._codomain = parent.codomain() |
|---|
| 43 | |
|---|
| 44 | def _repr_type(self): |
|---|
| 45 | return "Generic" |
|---|
| 46 | |
|---|
| 47 | def _repr_defn(self): |
|---|
| 48 | return "" |
|---|
| 49 | |
|---|
| 50 | def _repr_(self): |
|---|
| 51 | if self.is_endomorphism(): |
|---|
| 52 | s = "%s endomorphism of %s"%(self._repr_type(), self.domain()) |
|---|
| 53 | else: |
|---|
| 54 | s = "%s morphism:"%self._repr_type() |
|---|
| 55 | s += "\n From: %s"%self.domain() |
|---|
| 56 | s += "\n To: %s"%self.codomain() |
|---|
| 57 | d = self._repr_defn() |
|---|
| 58 | if d != '': |
|---|
| 59 | s += "\n Defn: %s"%('\n '.join(self._repr_defn().split('\n'))) |
|---|
| 60 | return s |
|---|
| 61 | |
|---|
| 62 | def domain(self): |
|---|
| 63 | return self._domain |
|---|
| 64 | |
|---|
| 65 | def codomain(self): |
|---|
| 66 | return self.parent().codomain() |
|---|
| 67 | |
|---|
| 68 | def category(self): |
|---|
| 69 | return self.parent().category() |
|---|
| 70 | |
|---|
| 71 | def is_endomorphism(self): |
|---|
| 72 | return self.parent().is_endomorphism_set() |
|---|
| 73 | |
|---|
| 74 | def _composition_(self, right, homset): |
|---|
| 75 | return FormalCompositeMorphism(homset, right, self) |
|---|
| 76 | |
|---|
| 77 | def __pow__(self, n, dummy): |
|---|
| 78 | if not self.is_endomorphism(): |
|---|
| 79 | raise TypeError, "self must be an endomorphism." |
|---|
| 80 | # todo -- what about the case n=0 -- need to specify the identity map somehow. |
|---|
| 81 | import sage.rings.arith as arith |
|---|
| 82 | return arith.generic_power(self, n) |
|---|
| 83 | |
|---|
| 84 | |
|---|
| 85 | |
|---|
| 86 | class SchemeMorphism(PyMorphism): |
|---|
| 87 | """ |
|---|
| 88 | A scheme morphism |
|---|
| 89 | """ |
|---|
| 90 | def __init__(self, parent): |
|---|
| 91 | PyMorphism.__init__(self, parent) |
|---|
| 92 | |
|---|
| 93 | def _repr_type(self): |
|---|
| 94 | return "Scheme" |
|---|
| 95 | |
|---|
| 96 | def glue_along_domains(self, other): |
|---|
| 97 | r""" |
|---|
| 98 | Assuming that self and other are open immersions with the same |
|---|
| 99 | domain, return scheme obtained by gluing along the images. |
|---|
| 100 | |
|---|
| 101 | EXAMPLES: |
|---|
| 102 | We construct a scheme isomorphic to the projective line over |
|---|
| 103 | $\Spec(\Q)$ by gluing two copies of $\A^1$ minus a point. |
|---|
| 104 | sage: R.<x,y> = MPolynomialRing(QQ, 2) |
|---|
| 105 | sage: S.<xbar, ybar> = R.quotient(x*y - 1) |
|---|
| 106 | sage: Rx = PolynomialRing(QQ, 'x') |
|---|
| 107 | sage: i1 = Rx.hom([xbar]) |
|---|
| 108 | sage: Ry = PolynomialRing(QQ, 'y') |
|---|
| 109 | sage: i2 = Ry.hom([ybar]) |
|---|
| 110 | sage: Sch = Schemes() |
|---|
| 111 | sage: f1 = Sch(i1) |
|---|
| 112 | sage: f2 = Sch(i2) |
|---|
| 113 | |
|---|
| 114 | Now f1 and f2 have the same domain, which is a $\A^1$ minus a point. |
|---|
| 115 | We glue along the domain: |
|---|
| 116 | sage: P1 = f1.glue_along_domains(f2) |
|---|
| 117 | sage: P1 |
|---|
| 118 | Scheme obtained by gluing X and Y along U, where |
|---|
| 119 | X: Spectrum of Univariate Polynomial Ring in x over Rational Field |
|---|
| 120 | Y: Spectrum of Univariate Polynomial Ring in y over Rational Field |
|---|
| 121 | U: Spectrum of Quotient of Polynomial Ring in x, y over Rational Field by the ideal (x*y - 1) |
|---|
| 122 | |
|---|
| 123 | sage: a, b = P1.gluing_maps() |
|---|
| 124 | sage: a |
|---|
| 125 | Affine Scheme morphism: |
|---|
| 126 | From: Spectrum of Quotient of Polynomial Ring in x, y over Rational Field by the ideal (x*y - 1) |
|---|
| 127 | To: Spectrum of Univariate Polynomial Ring in x over Rational Field |
|---|
| 128 | Defn: Ring morphism: |
|---|
| 129 | From: Univariate Polynomial Ring in x over Rational Field |
|---|
| 130 | To: Quotient of Polynomial Ring in x, y over Rational Field by the ideal (x*y - 1) |
|---|
| 131 | Defn: x |--> xbar |
|---|
| 132 | sage: b |
|---|
| 133 | Affine Scheme morphism: |
|---|
| 134 | From: Spectrum of Quotient of Polynomial Ring in x, y over Rational Field by the ideal (x*y - 1) |
|---|
| 135 | To: Spectrum of Univariate Polynomial Ring in y over Rational Field |
|---|
| 136 | Defn: Ring morphism: |
|---|
| 137 | From: Univariate Polynomial Ring in y over Rational Field |
|---|
| 138 | To: Quotient of Polynomial Ring in x, y over Rational Field by the ideal (x*y - 1) |
|---|
| 139 | Defn: y |--> ybar |
|---|
| 140 | """ |
|---|
| 141 | import glue |
|---|
| 142 | return glue.GluedScheme(self, other) |
|---|
| 143 | |
|---|
| 144 | class SchemeMorphism_id(SchemeMorphism): |
|---|
| 145 | """ |
|---|
| 146 | Return the identity morphism from X to itself. |
|---|
| 147 | |
|---|
| 148 | EXAMPLES: |
|---|
| 149 | sage: X = Spec(ZZ) |
|---|
| 150 | sage: X.identity_morphism() |
|---|
| 151 | Scheme endomorphism of Spectrum of Integer Ring |
|---|
| 152 | Defn: Identity map |
|---|
| 153 | """ |
|---|
| 154 | def __init__(self, X): |
|---|
| 155 | SchemeMorphism.__init__(self, X.Hom(X)) |
|---|
| 156 | |
|---|
| 157 | def _repr_defn(self): |
|---|
| 158 | return "Identity map" |
|---|
| 159 | |
|---|
| 160 | |
|---|
| 161 | class SchemeMorphism_structure_map(SchemeMorphism): |
|---|
| 162 | def __init__(self, parent): |
|---|
| 163 | """ |
|---|
| 164 | INPUT: |
|---|
| 165 | parent -- homset with codomain equal to the base scheme of the domain. |
|---|
| 166 | """ |
|---|
| 167 | SchemeMorphism.__init__(self, parent) |
|---|
| 168 | if self.domain().base_scheme() != self.codomain(): |
|---|
| 169 | raise ValueError, "parent must have codomain equal the base scheme of domain." |
|---|
| 170 | |
|---|
| 171 | def _repr_defn(self): |
|---|
| 172 | return "Structure map" |
|---|
| 173 | |
|---|
| 174 | |
|---|
| 175 | class SchemeMorphism_spec(SchemeMorphism): |
|---|
| 176 | """ |
|---|
| 177 | A morphism of spectrums of rings |
|---|
| 178 | |
|---|
| 179 | EXAMPLES: |
|---|
| 180 | sage: R.<x> = PolynomialRing(QQ) |
|---|
| 181 | sage: phi = R.hom([QQ(7)]); phi |
|---|
| 182 | Ring morphism: |
|---|
| 183 | From: Univariate Polynomial Ring in x over Rational Field |
|---|
| 184 | To: Rational Field |
|---|
| 185 | Defn: x |--> 7 |
|---|
| 186 | |
|---|
| 187 | sage: X = Spec(QQ); Y = Spec(R) |
|---|
| 188 | sage: f = X.hom(phi); f |
|---|
| 189 | Affine Scheme morphism: |
|---|
| 190 | From: Spectrum of Rational Field |
|---|
| 191 | To: Spectrum of Univariate Polynomial Ring in x over Rational Field |
|---|
| 192 | Defn: Ring morphism: |
|---|
| 193 | From: Univariate Polynomial Ring in x over Rational Field |
|---|
| 194 | To: Rational Field |
|---|
| 195 | Defn: x |--> 7 |
|---|
| 196 | |
|---|
| 197 | sage: f.ring_homomorphism() |
|---|
| 198 | Ring morphism: |
|---|
| 199 | From: Univariate Polynomial Ring in x over Rational Field |
|---|
| 200 | To: Rational Field |
|---|
| 201 | Defn: x |--> 7 |
|---|
| 202 | """ |
|---|
| 203 | def __init__(self, parent, phi, check=True): |
|---|
| 204 | SchemeMorphism.__init__(self, parent) |
|---|
| 205 | if check: |
|---|
| 206 | if not is_RingHomomorphism(phi): |
|---|
| 207 | raise TypeError, "phi (=%s) must be a ring homomorphism"%phi |
|---|
| 208 | if phi.domain() != parent.codomain().coordinate_ring(): |
|---|
| 209 | raise TypeError, "phi (=%s) must have domain %s"%(phi, |
|---|
| 210 | parent.codomain().coordinate_ring()) |
|---|
| 211 | if phi.codomain() != parent.domain().coordinate_ring(): |
|---|
| 212 | raise TypeError, "phi (=%s) must have codomain %s"%(phi, |
|---|
| 213 | parent.domain().coordinate_ring()) |
|---|
| 214 | self.__ring_homomorphism = phi |
|---|
| 215 | |
|---|
| 216 | def __call__(self, P): |
|---|
| 217 | if not is_SchemeTopologicalPoint(P) and P in self.domain(): |
|---|
| 218 | raise TypeError, "P (=%s) must be a topological scheme point of %s"%(P, self) |
|---|
| 219 | S = self.ring_homomorphism().inverse_image(P.prime_ideal()) |
|---|
| 220 | return self.codomain()(S) |
|---|
| 221 | |
|---|
| 222 | def _repr_type(self): |
|---|
| 223 | return "Affine Scheme" |
|---|
| 224 | |
|---|
| 225 | def _repr_defn(self): |
|---|
| 226 | return repr(self.ring_homomorphism()) |
|---|
| 227 | |
|---|
| 228 | |
|---|
| 229 | def ring_homomorphism(self): |
|---|
| 230 | return self.__ring_homomorphism |
|---|
| 231 | |
|---|
| 232 | |
|---|
| 233 | ############################################################################ |
|---|
| 234 | # Morphisms between schemes given on points |
|---|
| 235 | # The _affine and _projective below refer to the CODOMAIN. |
|---|
| 236 | # The domain can be either affine or projective irregardless |
|---|
| 237 | # of the class |
|---|
| 238 | ############################################################################ |
|---|
| 239 | |
|---|
| 240 | class SchemeMorphism_on_points(SchemeMorphism): |
|---|
| 241 | """ |
|---|
| 242 | A morphism of schemes determined by rational functions that define |
|---|
| 243 | what the morphism does on points in the ambient space. |
|---|
| 244 | """ |
|---|
| 245 | def __call__(self, x): |
|---|
| 246 | if not isinstance(x, SchemeMorphism_coordinates): |
|---|
| 247 | raise TypeError, "x (=%s) must be a projective point given by coordinates"%x |
|---|
| 248 | P = [f(x._coords) for f in self.defining_polynomials()] |
|---|
| 249 | return self.codomain()(P) |
|---|
| 250 | |
|---|
| 251 | |
|---|
| 252 | def _repr_defn(self): |
|---|
| 253 | i = self.domain().ambient_space()._repr_generic_point() |
|---|
| 254 | o = self.codomain().ambient_space()._repr_generic_point(self.defining_polynomials()) |
|---|
| 255 | return "Defined on coordinates by sending %s to\n%s"%(i,o) |
|---|
| 256 | |
|---|
| 257 | |
|---|
| 258 | class SchemeMorphism_on_points_affine_space(SchemeMorphism_on_points): |
|---|
| 259 | """ |
|---|
| 260 | A morphism of schemes determined by rational functions that define |
|---|
| 261 | what the morphism does on points in the ambient affine space. |
|---|
| 262 | """ |
|---|
| 263 | def __init__(self, parent, polys, check=True): |
|---|
| 264 | if check: |
|---|
| 265 | if not isinstance(polys, (list, tuple)): |
|---|
| 266 | raise TypeError, "polys (=%s) must be a list or tuple"%polys |
|---|
| 267 | polys = Sequence(polys) |
|---|
| 268 | if len(polys) != parent.codomain().dimension(): |
|---|
| 269 | raise ValueError, "there must be %s polynomials but instead received %s"%( |
|---|
| 270 | parent.codomain().dimension(), polys) |
|---|
| 271 | polys.set_immutable() |
|---|
| 272 | # Todo: check that map is well defined (how?) |
|---|
| 273 | self.__polys = polys |
|---|
| 274 | SchemeMorphism_on_points.__init__(self, parent) |
|---|
| 275 | |
|---|
| 276 | def defining_polynomials(self): |
|---|
| 277 | return self.__polys |
|---|
| 278 | |
|---|
| 279 | |
|---|
| 280 | class SchemeMorphism_on_points_projective_space(SchemeMorphism_on_points): |
|---|
| 281 | """ |
|---|
| 282 | A morphism of schemes determined by rational functions that define |
|---|
| 283 | what the morphism does on points in the ambient projective space. |
|---|
| 284 | """ |
|---|
| 285 | |
|---|
| 286 | def __init__(self, parent, polys, check=True): |
|---|
| 287 | if check: |
|---|
| 288 | if not isinstance(polys, (list, tuple)): |
|---|
| 289 | raise TypeError, "polys (=%s) must be a list or tuple"%polys |
|---|
| 290 | polys = Sequence(polys) |
|---|
| 291 | if len(polys) != parent.codomain().ambient_space().ngens(): |
|---|
| 292 | raise ValueError, "there must be %s polynomials"%parent.codomain().ambient_space().ngens() |
|---|
| 293 | polys.set_immutable() |
|---|
| 294 | self.__polys = polys |
|---|
| 295 | SchemeMorphism_on_points.__init__(self, parent) |
|---|
| 296 | |
|---|
| 297 | def defining_polynomials(self): |
|---|
| 298 | return self.__polys |
|---|
| 299 | |
|---|
| 300 | |
|---|
| 301 | ############################################################################ |
|---|
| 302 | # Rational points on schemes, which we view as morphisms determined |
|---|
| 303 | # by coordinates. |
|---|
| 304 | ############################################################################ |
|---|
| 305 | |
|---|
| 306 | class SchemeMorphism_coordinates(SchemeMorphism): |
|---|
| 307 | def _repr_(self): |
|---|
| 308 | return self.codomain().ambient_space()._repr_generic_point(self._coords) |
|---|
| 309 | |
|---|
| 310 | def _latex_(self): |
|---|
| 311 | return self.codomain().ambient_space()._latex_generic_point(self._coords) |
|---|
| 312 | |
|---|
| 313 | def __getitem__(self, n): |
|---|
| 314 | return self._coords[n] |
|---|
| 315 | |
|---|
| 316 | def __list__(self): |
|---|
| 317 | return list(self._coords) |
|---|
| 318 | |
|---|
| 319 | def __tuple__(self): |
|---|
| 320 | return self._coords |
|---|
| 321 | |
|---|
| 322 | def __cmp__(self, other): |
|---|
| 323 | if not isinstance(other, SchemeMorphism_coordinates): |
|---|
| 324 | try: |
|---|
| 325 | other = self.codomain().ambient_space()(other) |
|---|
| 326 | except TypeError: |
|---|
| 327 | return -1 |
|---|
| 328 | return cmp(self._coords, other._coords) |
|---|
| 329 | |
|---|
| 330 | def scheme(self): |
|---|
| 331 | return self.codomain() |
|---|
| 332 | |
|---|
| 333 | class SchemeMorphism_affine_coordinates(SchemeMorphism_coordinates): |
|---|
| 334 | """ |
|---|
| 335 | A morphism determined by giving coordinates in a ring. |
|---|
| 336 | |
|---|
| 337 | INPUT: |
|---|
| 338 | X -- a subscheme of an ambient affine space over a ring R. |
|---|
| 339 | v -- a list or tuple of coordinates in R |
|---|
| 340 | |
|---|
| 341 | EXAMPLES: |
|---|
| 342 | sage: A = AffineSpace(2, QQ) |
|---|
| 343 | sage: A(1,2) |
|---|
| 344 | (1, 2) |
|---|
| 345 | """ |
|---|
| 346 | def __init__(self, X, v, check=True): |
|---|
| 347 | if scheme.is_Scheme(X): |
|---|
| 348 | X = X(X.base_ring()) |
|---|
| 349 | SchemeMorphism.__init__(self, X) |
|---|
| 350 | if check: |
|---|
| 351 | # Verify that there are the right number of coords |
|---|
| 352 | d = X.codomain().ambient_space().ngens() |
|---|
| 353 | if len(v) != d: |
|---|
| 354 | raise TypeError, \ |
|---|
| 355 | "Argument v (=%s) must have %s coordinates."%(v, d) |
|---|
| 356 | if is_SchemeMorphism(v): |
|---|
| 357 | v = list(v) |
|---|
| 358 | if not isinstance(v,(list,tuple)): |
|---|
| 359 | raise TypeError, \ |
|---|
| 360 | "Argument v (= %s) must be a scheme point, list, or tuple."%str(v) |
|---|
| 361 | # Make sure the coordinates all lie in the appropriate ring |
|---|
| 362 | v = Sequence(v, X.value_ring()) |
|---|
| 363 | # Verify that the point satisfies the equations of X. |
|---|
| 364 | X.codomain()._check_satisfies_equations(v) |
|---|
| 365 | self._coords = v |
|---|
| 366 | |
|---|
| 367 | |
|---|
| 368 | class SchemeMorphism_projective_coordinates_ring(SchemeMorphism_coordinates): |
|---|
| 369 | """ |
|---|
| 370 | A morphism determined by giving coordinates in a ring (how?). |
|---|
| 371 | |
|---|
| 372 | """ |
|---|
| 373 | def __init__(self, X, v, check=True): |
|---|
| 374 | raise NotImplementedError |
|---|
| 375 | |
|---|
| 376 | |
|---|
| 377 | class SchemeMorphism_projective_coordinates_field(SchemeMorphism_projective_coordinates_ring): |
|---|
| 378 | """ |
|---|
| 379 | A morphism determined by giving coordinates in a field. |
|---|
| 380 | |
|---|
| 381 | INPUT: |
|---|
| 382 | X -- a subscheme of an ambient projective space over a field K |
|---|
| 383 | v -- a list or tuple of coordinates in K |
|---|
| 384 | |
|---|
| 385 | EXAMPLES: |
|---|
| 386 | sage: P = ProjectiveSpace(3, RR) |
|---|
| 387 | sage: P(2,3,4,5) |
|---|
| 388 | (0.400000000000000 : 0.600000000000000 : 0.800000000000000 : 1.00000000000000) |
|---|
| 389 | |
|---|
| 390 | sage: P = ProjectiveSpace(3, QQ) |
|---|
| 391 | sage: P(0,0,0,0) |
|---|
| 392 | Traceback (most recent call last): |
|---|
| 393 | ... |
|---|
| 394 | ValueError: [0, 0, 0, 0] does not define a valid point since all entries are 0 |
|---|
| 395 | """ |
|---|
| 396 | def __init__(self, X, v, check=True): |
|---|
| 397 | if scheme.is_Scheme(X): |
|---|
| 398 | X = X(X.base_ring()) |
|---|
| 399 | SchemeMorphism.__init__(self, X) |
|---|
| 400 | if check: |
|---|
| 401 | from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field # TODO: fix circular ref. |
|---|
| 402 | d = X.codomain().ambient_space().ngens() |
|---|
| 403 | if is_SchemeMorphism(v) or isinstance(v, EllipticCurvePoint_field): |
|---|
| 404 | v = list(v) |
|---|
| 405 | if not isinstance(v,(list,tuple)): |
|---|
| 406 | raise TypeError, \ |
|---|
| 407 | "Argument v (= %s) must be a scheme point, list, or tuple."%str(v) |
|---|
| 408 | if len(v) != d and len(v) != d-1: |
|---|
| 409 | raise TypeError, "v (=%s) must have %s components"%(v, d) |
|---|
| 410 | #v = Sequence(v, X.base_ring()) |
|---|
| 411 | v = Sequence(v, X.value_ring()) |
|---|
| 412 | if len(v) == d-1: # very common special case |
|---|
| 413 | v.append(1) |
|---|
| 414 | |
|---|
| 415 | n = len(v) |
|---|
| 416 | all_zero = True |
|---|
| 417 | for i in range(n): |
|---|
| 418 | if v[n-1-i]: |
|---|
| 419 | all_zero = False |
|---|
| 420 | c = v[n-1-i] |
|---|
| 421 | if c == 1: |
|---|
| 422 | break |
|---|
| 423 | for j in range(n-i): |
|---|
| 424 | v[j] /= c |
|---|
| 425 | break |
|---|
| 426 | if all_zero: |
|---|
| 427 | raise ValueError, "%s does not define a valid point since all entries are 0"%repr(v) |
|---|
| 428 | |
|---|
| 429 | X.codomain()._check_satisfies_equations(v) |
|---|
| 430 | |
|---|
| 431 | self._coords = v |
|---|
| 432 | |
|---|
| 433 | class SchemeMorphism_abelian_variety_coordinates_field(AdditiveGroupElement, SchemeMorphism_projective_coordinates_field): |
|---|
| 434 | def __mul__(self, n): |
|---|
| 435 | if isinstance(n, (RingElement, int, long)): |
|---|
| 436 | # [n]*P - multiplication by n. |
|---|
| 437 | return AdditiveGroupElement._rmul_(self, Integer(n)) |
|---|
| 438 | else: |
|---|
| 439 | # Function composition |
|---|
| 440 | return SchemeMorphism_projective_coordinates_field.__mul__(self, n) |
|---|