passagemath-gfan 10.6.32__cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of passagemath-gfan might be problematic. Click here for more details.

@@ -0,0 +1,1899 @@
1
+ # sage_setup: distribution = sagemath-gfan
2
+ # sage.doctest: optional - gfan
3
+ r"""
4
+ Groebner Fans
5
+
6
+ Sage provides much of the functionality of ``gfan``, which is a
7
+ software package whose main function is to enumerate all reduced
8
+ Groebner bases of a polynomial ideal. The reduced Groebner bases
9
+ yield the maximal cones in the Groebner fan of the ideal. Several
10
+ subcomputations can be issued and additional tools are included.
11
+ Among these the highlights are:
12
+
13
+ - Commands for computing tropical varieties.
14
+
15
+ - Interactive walks in the Groebner fan of an ideal.
16
+
17
+ - Commands for graphical renderings of Groebner fans and monomial
18
+ ideals.
19
+
20
+
21
+ AUTHORS:
22
+
23
+ - Anders Nedergaard Jensen: Wrote the ``gfan`` C++ program, which
24
+ implements algorithms many of which were invented by Jensen, Komei
25
+ Fukuda, and Rekha Thomas. All the underlying hard work of the
26
+ Groebner fans functionality of Sage depends on this C++ program.
27
+
28
+ - William Stein (2006-04-20): Wrote first version of the Sage code
29
+ for working with Groebner fans.
30
+
31
+ - Tristram Bogart: the design of the Sage interface
32
+ to ``gfan`` is joint work with Tristram Bogart, who also supplied
33
+ numerous examples.
34
+
35
+ - Marshall Hampton (2008-03-25): Rewrote various functions to use
36
+ ``gfan-0.3``. This is still a work in progress, comments are
37
+ appreciated on sage-devel@googlegroups.com (or personally at
38
+ hamptonio@gmail.com).
39
+
40
+ EXAMPLES::
41
+
42
+ sage: x,y = QQ['x,y'].gens()
43
+ sage: i = ideal(x^2 - y^2 + 1)
44
+ sage: g = i.groebner_fan()
45
+ sage: g.reduced_groebner_bases()
46
+ [[x^2 - y^2 + 1], [-x^2 + y^2 - 1]]
47
+
48
+ TESTS::
49
+
50
+ sage: x,y = QQ['x,y'].gens()
51
+ sage: i = ideal(x^2 - y^2 + 1)
52
+ sage: g = i.groebner_fan()
53
+ sage: g == loads(dumps(g))
54
+ True
55
+
56
+ REFERENCES:
57
+
58
+ - Anders N. Jensen; *Gfan, a software system for Groebner fans*;
59
+ http://home.math.au.dk/jensen/software/gfan/gfan.html
60
+ """
61
+ from subprocess import PIPE, Popen
62
+ import pexpect
63
+ import re
64
+ import string
65
+ from typing import Iterator
66
+
67
+ from sage.structure.sage_object import SageObject
68
+ from sage.interfaces.gfan import gfan
69
+ from .multi_polynomial_ideal import MPolynomialIdeal
70
+ from .polynomial_ring_constructor import PolynomialRing
71
+ from sage.rings.rational_field import QQ
72
+ from sage.rings.integer import Integer
73
+ from sage.rings.integer_ring import ZZ
74
+ from sage.modules.free_module_element import vector
75
+ from sage.misc.lazy_import import lazy_import
76
+ lazy_import("sage.plot.all", ["line", "Graphics", "polygon"])
77
+ lazy_import("sage.plot.plot3d.shapes2", "line3d")
78
+ from sage.geometry.polyhedron.constructor import Polyhedron
79
+ from sage.geometry.fan import Fan
80
+ from sage.matrix.constructor import matrix
81
+ from sage.misc.cachefunc import cached_method
82
+ from sage.misc.misc_c import prod
83
+
84
+
85
+ def prefix_check(str_list) -> bool:
86
+ """
87
+ Check if any strings in a list are prefixes of another string in
88
+ the list.
89
+
90
+ EXAMPLES::
91
+
92
+ sage: from sage.rings.polynomial.groebner_fan import prefix_check
93
+ sage: prefix_check(['z1','z1z1'])
94
+ False
95
+ sage: prefix_check(['z1','zz1'])
96
+ True
97
+ """
98
+ for index1, string1 in enumerate(str_list):
99
+ for index2, string2 in enumerate(str_list):
100
+ if index1 != index2:
101
+ if string1[:len(string2)] == string2:
102
+ return False
103
+ return True
104
+
105
+
106
+ def max_degree(list_of_polys) -> float:
107
+ """
108
+ Compute the maximum degree of a list of polynomials.
109
+
110
+ EXAMPLES::
111
+
112
+ sage: from sage.rings.polynomial.groebner_fan import max_degree
113
+ sage: R.<x,y> = PolynomialRing(QQ,2)
114
+ sage: p_list = [x^2-y,x*y^10-x]
115
+ sage: max_degree(p_list)
116
+ 11.0
117
+ """
118
+ return float(max(qf.degree() for qf in list_of_polys))
119
+
120
+
121
+ def _cone_parse(fan_dict_cone) -> dict:
122
+ """
123
+ Utility function that parses cone information into a dict indexed by
124
+ dimension.
125
+
126
+ INPUT:
127
+
128
+ - ``fan_dict_cone`` -- the value of a ``fan_dict`` with key 'CONES'
129
+
130
+ EXAMPLES::
131
+
132
+ sage: R.<x,y,z,w> = PolynomialRing(QQ,4)
133
+ sage: I = R.ideal([x^2+y^2+z^2-1,x^4-y^2-z-1,x+y+z,w+x+y])
134
+ sage: GF = I.groebner_fan()
135
+ sage: TI = GF.tropical_intersection()
136
+ sage: from sage.rings.polynomial.groebner_fan import _cone_parse
137
+ sage: _cone_parse(TI.fan_dict['CONES'])
138
+ {1: [[0], [1], [2], [3], [4]], 2: [[2, 3]]}
139
+ """
140
+ cone_dict = {}
141
+ cur_dim = 0
142
+ for item in fan_dict_cone:
143
+ temp = item.split('{')[-1]
144
+ if temp.split('}') == '':
145
+ temp = ['']
146
+ else:
147
+ temp = temp.split('}')[0]
148
+ temp = temp.split(' ')
149
+ if item.find('Dimension') != -1:
150
+ cur_dim = Integer(item.split(' ')[-1])
151
+ if cur_dim > 0:
152
+ cone_dict[cur_dim] = [[Integer(q) for q in temp if q != '']]
153
+ else:
154
+ if cur_dim > 0:
155
+ cone_dict[cur_dim] += [[Integer(q) for q in temp if q != '']]
156
+ return cone_dict
157
+
158
+
159
+ class PolyhedralCone(SageObject):
160
+
161
+ def __init__(self, gfan_polyhedral_cone, ring=QQ) -> None:
162
+ """
163
+ Convert polymake/gfan data on a polyhedral cone into a sage class.
164
+
165
+ Currently (18-03-2008) needs a lot of work.
166
+
167
+ EXAMPLES::
168
+
169
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
170
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
171
+ sage: a = gf[0].groebner_cone()
172
+ sage: a.facets()
173
+ [[0, 0, 1], [0, 1, 0], [1, 0, 0]]
174
+ """
175
+ cone_keys = ['AMBIENT_DIM', 'DIM', 'IMPLIED_EQUATIONS',
176
+ 'LINEALITY_DIM', 'LINEALITY_SPACE', 'FACETS',
177
+ 'RELATIVE_INTERIOR_POINT']
178
+ poly_lines = gfan_polyhedral_cone.split('\n')
179
+ self.cone_dict = {}
180
+ cur_key = None
181
+ for ting in poly_lines:
182
+ if cone_keys.count(ting):
183
+ cur_key = ting
184
+ self.cone_dict[cur_key] = []
185
+ elif cur_key and ting:
186
+ self.cone_dict[cur_key].append(ting)
187
+ self._facets = []
188
+ for facet in self.cone_dict['FACETS']:
189
+ temp_facet = facet.split('\t')[0]
190
+ temp_facet = temp_facet.split(' ')
191
+ temp_facet = [int(x) for x in temp_facet]
192
+ self._facets.append(temp_facet)
193
+ self._ambient_dim = int(self.cone_dict['AMBIENT_DIM'][0])
194
+ self._dim = int(self.cone_dict['DIM'][0])
195
+ self._lineality_dim = int(self.cone_dict['LINEALITY_DIM'][0])
196
+ rel_int_pt_str = self.cone_dict['RELATIVE_INTERIOR_POINT'][0]
197
+ self._relative_interior_point = [int(q) for q in rel_int_pt_str.split(' ')]
198
+
199
+ def _repr_(self) -> str:
200
+ """
201
+ Return a basic description of the polyhedral cone.
202
+
203
+ EXAMPLES::
204
+
205
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
206
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
207
+ sage: a = gf[0].groebner_cone()
208
+ sage: a # indirect doctests
209
+ Polyhedral cone in 3 dimensions of dimension 3
210
+ """
211
+ return "Polyhedral cone in {} dimensions of dimension {}".format(self.ambient_dim(), self.dim())
212
+
213
+ def facets(self) -> list:
214
+ """
215
+ Return the inward facet normals of the Groebner cone.
216
+
217
+ EXAMPLES::
218
+
219
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
220
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
221
+ sage: a = gf[0].groebner_cone()
222
+ sage: a.facets()
223
+ [[0, 0, 1], [0, 1, 0], [1, 0, 0]]
224
+ """
225
+ return self._facets
226
+
227
+ def ambient_dim(self):
228
+ """
229
+ Return the ambient dimension of the Groebner cone.
230
+
231
+ EXAMPLES::
232
+
233
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
234
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
235
+ sage: a = gf[0].groebner_cone()
236
+ sage: a.ambient_dim()
237
+ 3
238
+ """
239
+ return self._ambient_dim
240
+
241
+ def dim(self):
242
+ """
243
+ Return the dimension of the Groebner cone.
244
+
245
+ EXAMPLES::
246
+
247
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
248
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
249
+ sage: a = gf[0].groebner_cone()
250
+ sage: a.dim()
251
+ 3
252
+ """
253
+ return self._dim
254
+
255
+ def lineality_dim(self):
256
+ """
257
+ Return the lineality dimension of the Groebner cone. This is
258
+ just the difference between the ambient dimension and the dimension
259
+ of the cone.
260
+
261
+ EXAMPLES::
262
+
263
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
264
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
265
+ sage: a = gf[0].groebner_cone()
266
+ sage: a.lineality_dim()
267
+ 0
268
+ """
269
+ return self._lineality_dim
270
+
271
+ def relative_interior_point(self):
272
+ """
273
+ Return a point in the relative interior of the Groebner cone.
274
+
275
+ EXAMPLES::
276
+
277
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
278
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
279
+ sage: a = gf[0].groebner_cone()
280
+ sage: a.relative_interior_point()
281
+ [1, 1, 1]
282
+ """
283
+ return self._relative_interior_point
284
+
285
+
286
+ class PolyhedralFan(SageObject):
287
+ def __init__(self, gfan_polyhedral_fan, parameter_indices=None) -> None:
288
+ """
289
+ Convert polymake/gfan data on a polyhedral fan into a sage class.
290
+
291
+ INPUT:
292
+
293
+ - ``gfan_polyhedral_fan`` -- output from gfan of a polyhedral fan
294
+
295
+ EXAMPLES::
296
+
297
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
298
+ sage: i2 = ideal(x*z + 6*y*z - z^2, x*y + 6*x*z + y*z - z^2, y^2 + x*z + y*z)
299
+ sage: gf2 = i2.groebner_fan(verbose=False)
300
+ sage: pf = gf2.polyhedralfan()
301
+ sage: pf.rays()
302
+ [[-1, 0, 1], [-1, 1, 0], [1, -2, 1], [1, 1, -2], [2, -1, -1]]
303
+ """
304
+ if parameter_indices is None:
305
+ parameter_indices = []
306
+ fan_keys = ['AMBIENT_DIM', 'DIM', 'LINEALITY_DIM', 'RAYS', 'N_RAYS',
307
+ 'LINEALITY_SPACE', 'ORTH_LINEALITY_SPACE', 'F_VECTOR',
308
+ 'CONES', 'MAXIMAL_CONES', 'PURE', 'SIMPLICIAL',
309
+ 'MULTIPLICITIES']
310
+ poly_lines = gfan_polyhedral_fan.split('\n')
311
+ self.fan_dict = {}
312
+ cur_key = None
313
+ for ting in poly_lines:
314
+ if fan_keys.count(ting):
315
+ cur_key = ting
316
+ self.fan_dict[cur_key] = []
317
+ elif cur_key and ting != '':
318
+ self.fan_dict[cur_key].append(ting)
319
+ self._ambient_dim = int(self.fan_dict['AMBIENT_DIM'][0])
320
+ self._dim = int(self.fan_dict['DIM'][0])
321
+ self._lineality_dim = int(self.fan_dict['LINEALITY_DIM'][0])
322
+ self._rays = []
323
+ for ray in self.fan_dict['RAYS']:
324
+ temp_ray = ray.split('\t')[0]
325
+ temp_ray = temp_ray.split(' ')
326
+ temp_ray = [int(x) for x in temp_ray]
327
+ if parameter_indices != []:
328
+ for q in parameter_indices:
329
+ temp_ray = temp_ray[0:q] + [0] + temp_ray[q:]
330
+ self._rays.append(temp_ray)
331
+ self._cone_dict = _cone_parse(self.fan_dict['CONES'])
332
+ self._maximal_cone_dict = _cone_parse(self.fan_dict['MAXIMAL_CONES'])
333
+ self._str = gfan_polyhedral_fan
334
+
335
+ def _repr_(self) -> str:
336
+ """
337
+ Return a basic description of the polyhedral fan.
338
+
339
+ EXAMPLES::
340
+
341
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
342
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
343
+ sage: pf = gf.polyhedralfan()
344
+ sage: pf # indirect doctest
345
+ Polyhedral fan in 3 dimensions of dimension 3
346
+ """
347
+ return "Polyhedral fan in {} dimensions of dimension {}".format(self.ambient_dim(), self.dim())
348
+
349
+ def _str_(self) -> str:
350
+ r"""
351
+ Return the raw output of gfan as a string.
352
+
353
+ This should only be needed internally as all relevant output
354
+ is converted to sage objects.
355
+
356
+ EXAMPLES::
357
+
358
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
359
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
360
+ sage: pf = gf.polyhedralfan()
361
+ sage: pf._str_()
362
+ '_application fan\n_version 2.2\n_type SymmetricFan\n\nAMBIENT_DIM\n3\n\nDIM\n3\n\nLINEALITY_DIM\n0\n\nRAYS\n0 0 1\t# 0\n0 1 0\t# 1\n1 0 0\t# 2\n\nN_RAYS\n3\n\nLINEALITY_SPACE\n\nORTH_LINEALITY_SPACE\n1 0 0\n0 1 0\n0 0 1\n\nF_VECTOR\n1 3 3 1\n\nSIMPLICIAL\n1\n\nPURE\n1\n\nCONES\n{}\t# Dimension 0\n{0}\t# Dimension 1\n{1}\n{2}\n{0 1}\t# Dimension 2\n{0 2}\n{1 2}\n{0 1 2}\t# Dimension 3\n\nMAXIMAL_CONES\n{0 1 2}\t# Dimension 3\n'
363
+ """
364
+ return self._str
365
+
366
+ def ambient_dim(self):
367
+ """
368
+ Return the ambient dimension of the Groebner fan.
369
+
370
+ EXAMPLES::
371
+
372
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
373
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
374
+ sage: a = gf.polyhedralfan()
375
+ sage: a.ambient_dim()
376
+ 3
377
+ """
378
+ return self._ambient_dim
379
+
380
+ def dim(self):
381
+ """
382
+ Return the dimension of the Groebner fan.
383
+
384
+ EXAMPLES::
385
+
386
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
387
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
388
+ sage: a = gf.polyhedralfan()
389
+ sage: a.dim()
390
+ 3
391
+ """
392
+ return self._dim
393
+
394
+ def lineality_dim(self):
395
+ """
396
+ Return the lineality dimension of the fan. This is the
397
+ dimension of the largest subspace contained in the fan.
398
+
399
+ EXAMPLES::
400
+
401
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
402
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-2]).groebner_fan()
403
+ sage: a = gf.polyhedralfan()
404
+ sage: a.lineality_dim()
405
+ 0
406
+ """
407
+ return self._lineality_dim
408
+
409
+ def rays(self) -> list:
410
+ """
411
+ A list of rays of the polyhedral fan.
412
+
413
+ EXAMPLES::
414
+
415
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
416
+ sage: i2 = ideal(x*z + 6*y*z - z^2, x*y + 6*x*z + y*z - z^2, y^2 + x*z + y*z)
417
+ sage: gf2 = i2.groebner_fan(verbose=False)
418
+ sage: pf = gf2.polyhedralfan()
419
+ sage: pf.rays()
420
+ [[-1, 0, 1], [-1, 1, 0], [1, -2, 1], [1, 1, -2], [2, -1, -1]]
421
+ """
422
+ return sorted(self._rays)
423
+
424
+ def cones(self) -> dict:
425
+ """
426
+ A dictionary of cones in which the keys are the cone dimensions.
427
+
428
+ For each dimension, the value is a list of the cones,
429
+ where each element consists of a list of ray indices.
430
+
431
+ EXAMPLES::
432
+
433
+ sage: R.<x,y,z> = QQ[]
434
+ sage: f = 1+x+y+x*y
435
+ sage: I = R.ideal([f+z*f, 2*f+z*f, 3*f+z^2*f])
436
+ sage: GF = I.groebner_fan()
437
+ sage: PF = GF.tropical_intersection()
438
+ sage: PF.cones()
439
+ {1: [[0], [1], [2], [3], [4], [5]], 2: [[0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [4, 5]]}
440
+ """
441
+ return self._cone_dict
442
+
443
+ def maximal_cones(self) -> dict:
444
+ """
445
+ A dictionary of the maximal cones in which the keys are the
446
+ cone dimensions.
447
+
448
+ For each dimension, the value is a list of
449
+ the maximal cones, where each element consists of a list of ray indices.
450
+
451
+ EXAMPLES::
452
+
453
+ sage: R.<x,y,z> = QQ[]
454
+ sage: f = 1+x+y+x*y
455
+ sage: I = R.ideal([f+z*f, 2*f+z*f, 3*f+z^2*f])
456
+ sage: GF = I.groebner_fan()
457
+ sage: PF = GF.tropical_intersection()
458
+ sage: PF.maximal_cones()
459
+ {2: [[0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [4, 5]]}
460
+ """
461
+ return self._maximal_cone_dict
462
+
463
+ def f_vector(self) -> list:
464
+ """
465
+ The f-vector of the fan.
466
+
467
+ EXAMPLES::
468
+
469
+ sage: R.<x,y,z> = QQ[]
470
+ sage: f = 1+x+y+x*y
471
+ sage: I = R.ideal([f+z*f, 2*f+z*f, 3*f+z^2*f])
472
+ sage: GF = I.groebner_fan()
473
+ sage: PF = GF.tropical_intersection()
474
+ sage: PF.f_vector()
475
+ [1, 6, 12]
476
+ """
477
+ str_data = self.fan_dict['F_VECTOR'][0]
478
+ return [Integer(x) for x in str_data.split(' ')]
479
+
480
+ def is_simplicial(self) -> bool:
481
+ """
482
+ Whether the fan is simplicial or not.
483
+
484
+ EXAMPLES::
485
+
486
+ sage: R.<x,y,z> = QQ[]
487
+ sage: f = 1+x+y+x*y
488
+ sage: I = R.ideal([f+z*f, 2*f+z*f, 3*f+z^2*f])
489
+ sage: GF = I.groebner_fan()
490
+ sage: PF = GF.tropical_intersection()
491
+ sage: PF.is_simplicial()
492
+ True
493
+ """
494
+ return bool(int(self.fan_dict['SIMPLICIAL'][0]))
495
+
496
+ def to_RationalPolyhedralFan(self):
497
+ """
498
+ Convert to the RationalPolyhedralFan class, which is more actively
499
+ maintained.
500
+
501
+ While the information in each class is essentially the
502
+ same, the methods and implementation are different.
503
+
504
+ EXAMPLES::
505
+
506
+ sage: R.<x,y,z> = QQ[]
507
+ sage: f = 1+x+y+x*y
508
+ sage: I = R.ideal([f+z*f, 2*f+z*f, 3*f+z^2*f])
509
+ sage: GF = I.groebner_fan()
510
+ sage: PF = GF.tropical_intersection()
511
+ sage: fan = PF.to_RationalPolyhedralFan()
512
+ sage: [tuple(q.facet_normals()) for q in fan]
513
+ [(M(0, -1, 0), M(-1, 0, 0)), (M(0, 0, -1), M(-1, 0, 0)), (M(0, 0, 1), M(-1, 0, 0)), (M(0, 1, 0), M(-1, 0, 0)), (M(0, 0, -1), M(0, -1, 0)), (M(0, 0, 1), M(0, -1, 0)), (M(0, 1, 0), M(0, 0, -1)), (M(0, 1, 0), M(0, 0, 1)), (M(1, 0, 0), M(0, -1, 0)), (M(1, 0, 0), M(0, 0, -1)), (M(1, 0, 0), M(0, 0, 1)), (M(1, 0, 0), M(0, 1, 0))]
514
+
515
+ Here we use the RationalPolyhedralFan's Gale_transform method on a tropical
516
+ prevariety.
517
+
518
+ .. link
519
+
520
+ ::
521
+
522
+ sage: fan.Gale_transform() # needs sage.libs.linbox
523
+ [ 1 0 0 0 0 1 -2]
524
+ [ 0 1 0 0 1 0 -2]
525
+ [ 0 0 1 1 0 0 -2]
526
+ """
527
+ try:
528
+ return self._fan
529
+ except AttributeError:
530
+ cdnt = []
531
+ cones = self.cones()
532
+ for x in cones:
533
+ if x > 1:
534
+ cdnt += cones[x]
535
+ fan = Fan(cones=cdnt, rays=self.rays(), discard_faces=True)
536
+ self._fan = fan
537
+ return self._fan
538
+
539
+
540
+ class InitialForm(SageObject):
541
+ def __init__(self, cone, rays, initial_forms) -> None:
542
+ """
543
+ A system of initial forms from a polynomial system.
544
+
545
+ To each form is associated a cone and a list of
546
+ polynomials (the initial form system itself).
547
+
548
+ This class is intended for internal use inside of the
549
+ :class:`TropicalPrevariety` class.
550
+
551
+ EXAMPLES::
552
+
553
+ sage: from sage.rings.polynomial.groebner_fan import InitialForm
554
+ sage: R.<x,y> = QQ[]
555
+ sage: inform = InitialForm([0], [[-1, 0]], [y^2 - 1, y^2 - 2, y^2 - 3])
556
+ sage: inform._cone
557
+ [0]
558
+ """
559
+ self._cone = cone
560
+ self._rays = rays
561
+ self._initial_forms = initial_forms
562
+
563
+ def cone(self):
564
+ """
565
+ The cone associated with the initial form system.
566
+
567
+ EXAMPLES::
568
+
569
+ sage: R.<x,y> = QQ[]
570
+ sage: I = R.ideal([(x+y)^2-1,(x+y)^2-2,(x+y)^2-3])
571
+ sage: GF = I.groebner_fan()
572
+ sage: PF = GF.tropical_intersection()
573
+ sage: pfi0 = PF.initial_form_systems()[0]
574
+ sage: pfi0.cone()
575
+ [0]
576
+ """
577
+ return self._cone
578
+
579
+ def rays(self):
580
+ """
581
+ The rays of the cone associated with the initial form system.
582
+
583
+ EXAMPLES::
584
+
585
+ sage: R.<x,y> = QQ[]
586
+ sage: I = R.ideal([(x+y)^2-1,(x+y)^2-2,(x+y)^2-3])
587
+ sage: GF = I.groebner_fan()
588
+ sage: PF = GF.tropical_intersection()
589
+ sage: pfi0 = PF.initial_form_systems()[0]
590
+ sage: pfi0.rays()
591
+ [[-1, 0]]
592
+ """
593
+ return self._rays
594
+
595
+ def internal_ray(self):
596
+ """
597
+ A ray internal to the cone associated with the initial form
598
+ system.
599
+
600
+ EXAMPLES::
601
+
602
+ sage: R.<x,y> = QQ[]
603
+ sage: I = R.ideal([(x+y)^2-1,(x+y)^2-2,(x+y)^2-3])
604
+ sage: GF = I.groebner_fan()
605
+ sage: PF = GF.tropical_intersection()
606
+ sage: pfi0 = PF.initial_form_systems()[0]
607
+ sage: pfi0.internal_ray()
608
+ (-1, 0)
609
+ """
610
+ return sum([vector(q) for q in self.rays()])
611
+
612
+ def initial_forms(self):
613
+ """
614
+ The initial forms (polynomials).
615
+
616
+ EXAMPLES::
617
+
618
+ sage: R.<x,y> = QQ[]
619
+ sage: I = R.ideal([(x+y)^2-1,(x+y)^2-2,(x+y)^2-3])
620
+ sage: GF = I.groebner_fan()
621
+ sage: PF = GF.tropical_intersection()
622
+ sage: pfi0 = PF.initial_form_systems()[0]
623
+ sage: pfi0.initial_forms()
624
+ [y^2 - 1, y^2 - 2, y^2 - 3]
625
+ """
626
+ return self._initial_forms
627
+
628
+
629
+ def verts_for_normal(normal, poly) -> list:
630
+ """
631
+ Return the exponents of the vertices of a Newton polytope
632
+ that make up the supporting hyperplane for the given outward
633
+ normal.
634
+
635
+ EXAMPLES::
636
+
637
+ sage: from sage.rings.polynomial.groebner_fan import verts_for_normal
638
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
639
+ sage: f1 = x*y*z - 1
640
+ sage: f2 = f1*(x^2 + y^2 + 1)
641
+ sage: verts_for_normal([1,1,1],f2)
642
+ [(3, 1, 1), (1, 3, 1)]
643
+ """
644
+ exps = [tuple(x) for x in poly.exponents()]
645
+ expmat = matrix(exps)
646
+ vals = expmat * vector(QQ, normal)
647
+ maxval = max(vals)
648
+ return [exps[i] for i in range(len(exps)) if vals[i] == maxval]
649
+
650
+
651
+ class TropicalPrevariety(PolyhedralFan):
652
+
653
+ def __init__(self, gfan_polyhedral_fan, polynomial_system, poly_ring,
654
+ parameters=None) -> None:
655
+ """
656
+ This class is a subclass of the PolyhedralFan class,
657
+ with some additional methods for tropical prevarieties.
658
+
659
+ INPUT:
660
+
661
+ - ``gfan_polyhedral_fan`` -- output from ``gfan`` of a polyhedral fan
662
+ - ``polynomial_system`` -- list of polynomials
663
+ - ``poly_ring`` -- the polynomial ring of the list of polynomials
664
+ - ``parameters`` -- (optional) list of variables to be considered
665
+ as parameters
666
+
667
+ EXAMPLES::
668
+
669
+ sage: R.<x,y,z> = QQ[]
670
+ sage: I = R.ideal([(x+y+z)^2-1,(x+y+z)-x,(x+y+z)-3])
671
+ sage: GF = I.groebner_fan()
672
+ sage: TI = GF.tropical_intersection()
673
+ sage: TI._polynomial_system
674
+ [x^2 + 2*x*y + y^2 + 2*x*z + 2*y*z + z^2 - 1, y + z, x + y + z - 3]
675
+ """
676
+ parameter_indices = []
677
+ if parameters is not None:
678
+ allvars = poly_ring.gens()
679
+ parameter_indices = [allvars.index(q) for q in parameters]
680
+ PolyhedralFan.__init__(self, gfan_polyhedral_fan,
681
+ parameter_indices=parameter_indices)
682
+ self._polynomial_system = polynomial_system
683
+ self._parameters = parameters
684
+
685
+ def initial_form_systems(self) -> list:
686
+ """
687
+ Return a list of systems of initial forms for each cone
688
+ in the tropical prevariety.
689
+
690
+ EXAMPLES::
691
+
692
+ sage: R.<x,y> = QQ[]
693
+ sage: I = R.ideal([(x+y)^2-1,(x+y)^2-2,(x+y)^2-3])
694
+ sage: GF = I.groebner_fan()
695
+ sage: PF = GF.tropical_intersection()
696
+ sage: pfi = PF.initial_form_systems()
697
+ sage: for q in pfi:
698
+ ....: print(q.initial_forms())
699
+ [y^2 - 1, y^2 - 2, y^2 - 3]
700
+ [x^2 - 1, x^2 - 2, x^2 - 3]
701
+ [x^2 + 2*x*y + y^2, x^2 + 2*x*y + y^2, x^2 + 2*x*y + y^2]
702
+ """
703
+ try:
704
+ return self._initial_form_systems
705
+ except AttributeError:
706
+ initial_form_systems = []
707
+ pvars = self._polynomial_system[0].parent().gens()
708
+ nvars = len(pvars)
709
+ for dcone in self.cones():
710
+ for acone in self.cones()[dcone]:
711
+ rays = [self.rays()[i] for i in acone]
712
+ repray = sum([vector(q) for q in rays])
713
+ iforms = []
714
+ for poly in self._polynomial_system:
715
+ verts = verts_for_normal(repray, poly)
716
+ nform = 0
717
+ for x in verts:
718
+ factorlist = [pvars[i]**x[i] for i in range(nvars)]
719
+ temp_monomial = prod(factorlist)
720
+ nform += poly.monomial_coefficient(temp_monomial) * temp_monomial
721
+ iforms.append(nform)
722
+ initial_form_systems.append(InitialForm(acone, rays, iforms))
723
+ self._initial_form_systems = initial_form_systems
724
+ return self._initial_form_systems
725
+
726
+
727
+ def ring_to_gfan_format(input_ring) -> str:
728
+ """
729
+ Convert a ring to gfan's format.
730
+
731
+ EXAMPLES::
732
+
733
+ sage: R.<w,x,y,z> = QQ[]
734
+ sage: from sage.rings.polynomial.groebner_fan import ring_to_gfan_format
735
+ sage: ring_to_gfan_format(R)
736
+ 'Q[w, x, y, z]'
737
+ sage: R2.<x,y> = GF(2)[]
738
+ sage: ring_to_gfan_format(R2)
739
+ 'Z/2Z[x, y]'
740
+ """
741
+ gens = str(input_ring.gens()).replace('(', '[').replace(')', ']')
742
+ if input_ring.base_ring() is QQ:
743
+ return 'Q' + gens
744
+ elif input_ring.base_ring() is ZZ:
745
+ return 'Z' + gens
746
+ else:
747
+ return 'Z/{}Z'.format(input_ring.characteristic()) + gens
748
+
749
+
750
+ def ideal_to_gfan_format(input_ring, polys) -> str:
751
+ """
752
+ Return the ideal in gfan's notation.
753
+
754
+ EXAMPLES::
755
+
756
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
757
+ sage: polys = [x^2*y - z, y^2*z - x, z^2*x - y]
758
+ sage: from sage.rings.polynomial.groebner_fan import ideal_to_gfan_format
759
+ sage: ideal_to_gfan_format(R, polys)
760
+ 'Q[x, y, z]{x^2*y-z,y^2*z-x,x*z^2-y}'
761
+
762
+ TESTS:
763
+
764
+ Test that :issue:`20146` is fixed::
765
+
766
+ sage: P = PolynomialRing(QQ,"x11,x12,x13,x14,x15,x21,x22,x23,x24,x25,x31,x32,x33,x34,x35"); x = P.gens(); M = Matrix(3,x)
767
+ sage: I = P.ideal(M.minors(2))
768
+ sage: ideal_to_gfan_format(P,I.gens())
769
+ 'Q[x11, x12, x13, x14, x15, x21, x22, x23, x24, x25, x31, x32, x33, x34, x35]{-x12*x21+x11*x22,-x13*x21+x11*x23,-x14*x21+x11*x24,-x15*x21+x11*x25,-x13*x22+x12*x23,-x14*x22+x12*x24,-x15*x22+x12*x25,-x14*x23+x13*x24,-x15*x23+x13*x25,-x15*x24+x14*x25,-x12*x31+x11*x32,-x13*x31+x11*x33,-x14*x31+x11*x34,-x15*x31+x11*x35,-x13*x32+x12*x33,-x14*x32+x12*x34,-x15*x32+x12*x35,-x14*x33+x13*x34,-x15*x33+x13*x35,-x15*x34+x14*x35,-x22*x31+x21*x32,-x23*x31+x21*x33,-x24*x31+x21*x34,-x25*x31+x21*x35,-x23*x32+x22*x33,-x24*x32+x22*x34,-x25*x32+x22*x35,-x24*x33+x23*x34,-x25*x33+x23*x35,-x25*x34+x24*x35}'
770
+ """
771
+ ideal_gen_str = "{" + ",".join(str(poly).replace(" ", "").replace("'", "")
772
+ for poly in polys) + "}"
773
+ ring_str = ring_to_gfan_format(input_ring)
774
+ output = ring_str + ideal_gen_str
775
+ return output
776
+
777
+
778
+ class GroebnerFan(SageObject):
779
+
780
+ def __init__(self, I, is_groebner_basis=False, symmetry=None, verbose=False) -> None:
781
+ """
782
+ This class is used to access capabilities of the program ``Gfan``.
783
+
784
+ In addition to computing Groebner fans, ``Gfan`` can compute
785
+ other things in tropical geometry such as tropical prevarieties.
786
+
787
+ INPUT:
788
+
789
+ - ``I`` -- ideal in a multivariate polynomial ring
790
+
791
+ - ``is_groebner_basis`` -- boolean (default: ``False``); if
792
+ ``True``, then I.gens() must be a Groebner basis with respect to the
793
+ standard degree lexicographic term order
794
+
795
+ - ``symmetry`` -- (default: ``None``) if not ``None``, describes
796
+ symmetries of the ideal
797
+
798
+ - ``verbose`` -- (default: ``False``) if ``True``, printout
799
+ useful info during computations
800
+
801
+ EXAMPLES::
802
+
803
+ sage: R.<x,y,z> = QQ[]
804
+ sage: I = R.ideal([x^2*y - z, y^2*z - x, z^2*x - y])
805
+ sage: G = I.groebner_fan(); G
806
+ Groebner fan of the ideal:
807
+ Ideal (x^2*y - z, y^2*z - x, x*z^2 - y) of Multivariate Polynomial Ring in x, y, z over Rational Field
808
+
809
+ Here is an example of the use of the tropical_intersection command, and then using the RationalPolyhedralFan
810
+ class to compute the Stanley-Reisner ideal of the tropical prevariety::
811
+
812
+ sage: R.<x,y,z> = QQ[]
813
+ sage: I = R.ideal([(x+y+z)^3-1,(x+y+z)^3-x,(x+y+z)-3])
814
+ sage: GF = I.groebner_fan()
815
+ sage: PF = GF.tropical_intersection()
816
+ sage: PF.rays()
817
+ [[-1, 0, 0], [0, -1, 0], [0, 0, -1], [1, 1, 1]]
818
+ sage: RPF = PF.to_RationalPolyhedralFan()
819
+ sage: RPF.Stanley_Reisner_ideal(PolynomialRing(QQ,4,'A, B, C, D'))
820
+ Ideal (A*B, A*C, B*C*D) of Multivariate Polynomial Ring in A, B, C, D over Rational Field
821
+ """
822
+ self.__is_groebner_basis = is_groebner_basis
823
+ self.__symmetry = symmetry
824
+ if symmetry:
825
+ print("WARNING! Symmetry option not yet implemented!!")
826
+ self.__verbose = verbose
827
+ if not isinstance(I, MPolynomialIdeal):
828
+ raise TypeError("I must be a multivariate polynomial ideal")
829
+ if not prefix_check([str(R_gen) for R_gen in I.ring().gens()]):
830
+ raise RuntimeError("Ring variables cannot contain each other as prefixes")
831
+ S = I.ring()
832
+ R = S.base_ring()
833
+ # todo: add support for ZZ, which only works for bases computation, not tropical intersections
834
+ if not R.is_field():
835
+ raise NotImplementedError("Groebner fan computation only implemented over fields")
836
+ if not (R is QQ or (R.is_finite() and R.is_prime_field() and R.order() <= 32749)):
837
+ # 32749 is previous_prime(2^15)
838
+ raise NotImplementedError("Groebner fan computation only implemented over Q or GF(p) for p <= 32749.")
839
+ if S.ngens() > 52:
840
+ raise NotImplementedError("Groebner fan computation only implemented for rings in at most 52 variables.")
841
+
842
+ self.__ideal = I
843
+ self.__ring = S
844
+
845
+ def _repr_(self) -> str:
846
+ """
847
+ Describe the Groebner fan and its corresponding ideal.
848
+
849
+ EXAMPLES::
850
+
851
+ sage: R.<q,u> = PolynomialRing(QQ,2)
852
+ sage: gf = R.ideal([q-u,u^2-1]).groebner_fan()
853
+ sage: gf # indirect doctest
854
+ Groebner fan of the ideal:
855
+ Ideal (q - u, u^2 - 1) of Multivariate Polynomial Ring in q, u over Rational Field
856
+ """
857
+ return "Groebner fan of the ideal:\n{}".format(self.__ideal)
858
+
859
+ def __eq__(self, right) -> bool:
860
+ """
861
+ Test equality of Groebner fan objects.
862
+
863
+ EXAMPLES::
864
+
865
+ sage: R.<q,u> = PolynomialRing(QQ,2)
866
+ sage: gf = R.ideal([q^2-u,u^2-q]).groebner_fan()
867
+ sage: gf2 = R.ideal([u^2-q,q^2-u]).groebner_fan()
868
+ sage: gf.__eq__(gf2)
869
+ True
870
+ """
871
+ return type(self) is type(right) and self.ideal() == right.ideal()
872
+
873
+ def ideal(self):
874
+ """
875
+ Return the ideal the was used to define this Groebner fan.
876
+
877
+ EXAMPLES::
878
+
879
+ sage: R.<x1,x2> = PolynomialRing(QQ,2)
880
+ sage: gf = R.ideal([x1^3-x2,x2^3-2*x1-2]).groebner_fan()
881
+ sage: gf.ideal()
882
+ Ideal (x1^3 - x2, x2^3 - 2*x1 - 2) of Multivariate Polynomial Ring in x1, x2 over Rational Field
883
+ """
884
+ return self.__ideal
885
+
886
+ @cached_method
887
+ def _gfan_maps(self) -> tuple:
888
+ """
889
+ OUTPUT:
890
+
891
+ - map from Sage ring to ``gfan`` ring
892
+
893
+ - map from ``gfan`` ring to Sage ring
894
+
895
+ EXAMPLES::
896
+
897
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
898
+ sage: G = R.ideal([x^2*y - z, y^2*z - x, z^2*x - y]).groebner_fan()
899
+ sage: G._gfan_maps()
900
+ (Ring morphism:
901
+ From: Multivariate Polynomial Ring in x, y, z over Rational Field
902
+ To: Multivariate Polynomial Ring in a, b, c over Rational Field
903
+ Defn: x |--> a
904
+ y |--> b
905
+ z |--> c,
906
+ Ring morphism:
907
+ From: Multivariate Polynomial Ring in a, b, c over Rational Field
908
+ To: Multivariate Polynomial Ring in x, y, z over Rational Field
909
+ Defn: a |--> x
910
+ b |--> y
911
+ c |--> z)
912
+ """
913
+ S = self.__ring
914
+ n = S.ngens()
915
+
916
+ # Define a polynomial ring in n variables
917
+ # that are named a,b,c,d, ..., z, A, B, C, ...
918
+ R = S.base_ring()
919
+ T = PolynomialRing(R, n, string.ascii_letters[:n])
920
+
921
+ # Define the homomorphism that sends the
922
+ # generators of S to the generators of T.
923
+ phi = S.hom(T.gens())
924
+
925
+ # Define the homomorphism that sends the
926
+ # generators of T to the generators of S.
927
+ psi = T.hom(S.gens())
928
+ return (phi, psi)
929
+
930
+ def _gfan_ring(self) -> str:
931
+ """
932
+ Return the ring in ``gfan`` notation.
933
+
934
+ EXAMPLES::
935
+
936
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
937
+ sage: G = R.ideal([x^2*y - z, y^2*z - x, z^2*x - y]).groebner_fan()
938
+ sage: G._gfan_ring()
939
+ 'Q[x, y, z]'
940
+ """
941
+ return ring_to_gfan_format(self.ring())
942
+
943
+ @cached_method
944
+ def _gfan_ideal(self) -> str:
945
+ """
946
+ Return the ideal in ``gfan`` notation.
947
+
948
+ EXAMPLES::
949
+
950
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
951
+ sage: G = R.ideal([x^2*y - z, y^2*z - x, z^2*x - y]).groebner_fan()
952
+ sage: G._gfan_ideal()
953
+ 'Q[x, y, z]{x^2*y-z,y^2*z-x,x*z^2-y}'
954
+ """
955
+ return ideal_to_gfan_format(self.ring(),
956
+ self.__ideal.gens())
957
+
958
+ def weight_vectors(self) -> list:
959
+ """
960
+ Return the weight vectors corresponding to the reduced Groebner
961
+ bases.
962
+
963
+ EXAMPLES::
964
+
965
+ sage: r3.<x,y,z> = PolynomialRing(QQ,3)
966
+ sage: g = r3.ideal([x^3+y,y^3-z,z^2-x]).groebner_fan()
967
+ sage: g.weight_vectors()
968
+ [(3, 7, 1), (5, 1, 2), (7, 1, 4), (5, 1, 4), (1, 1, 1), (1, 4, 8), (1, 4, 10)]
969
+ sage: r4.<x,y,z,w> = PolynomialRing(QQ,4)
970
+ sage: g4 = r4.ideal([x^3+y,y^3-z,z^2-x,z^3 - w]).groebner_fan()
971
+ sage: len(g4.weight_vectors())
972
+ 23
973
+ """
974
+ gfan_processes = Popen(['gfan', '_weightvector', '-m'],
975
+ stdin=PIPE, stdout=PIPE, stderr=PIPE)
976
+ b_ans, _ = gfan_processes.communicate(input=self.gfan().encode("utf8"))
977
+ s_ans = b_ans.decode()
978
+ vect = re.compile(r"\([0-9,/\s]*\)")
979
+ ans = (tup[1:-1].split(',') for tup in vect.findall(s_ans))
980
+ return [vector(QQ, [QQ(y) for y in x]) for x in ans]
981
+
982
+ def ring(self):
983
+ """
984
+ Return the multivariate polynomial ring.
985
+
986
+ EXAMPLES::
987
+
988
+ sage: R.<x1,x2> = PolynomialRing(QQ,2)
989
+ sage: gf = R.ideal([x1^3-x2,x2^3-x1-2]).groebner_fan()
990
+ sage: gf.ring()
991
+ Multivariate Polynomial Ring in x1, x2 over Rational Field
992
+ """
993
+ return self.__ring
994
+
995
+ @cached_method
996
+ def _gfan_reduced_groebner_bases(self) -> str:
997
+ """
998
+ A string of the reduced Groebner bases of the ideal as output by
999
+ ``gfan``.
1000
+
1001
+ EXAMPLES::
1002
+
1003
+ sage: R.<a,b> = PolynomialRing(QQ,2)
1004
+ sage: gf = R.ideal([a^3-b^2,b^2-a-1]).groebner_fan()
1005
+ sage: gf._gfan_reduced_groebner_bases()
1006
+ 'Q[a,b]{{b^6-1+2*b^2-3*b^4,a+1-b^2},{a^3-1-a,b^2-1-a}}'
1007
+ """
1008
+ B = self.gfan(cmd='bases')
1009
+ B = B.replace('\n', '')
1010
+ return B
1011
+
1012
+ def characteristic(self):
1013
+ """
1014
+ Return the characteristic of the base ring.
1015
+
1016
+ EXAMPLES::
1017
+
1018
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
1019
+ sage: i1 = ideal(x*z + 6*y*z - z^2, x*y + 6*x*z + y*z - z^2, y^2 + x*z + y*z)
1020
+ sage: gf = i1.groebner_fan()
1021
+ sage: gf.characteristic()
1022
+ 0
1023
+ """
1024
+ return self.__ring.characteristic()
1025
+
1026
+ @cached_method
1027
+ def reduced_groebner_bases(self):
1028
+ """
1029
+ EXAMPLES::
1030
+
1031
+ sage: R.<x,y,z> = PolynomialRing(QQ, 3, order='lex')
1032
+ sage: G = R.ideal([x^2*y - z, y^2*z - x, z^2*x - y]).groebner_fan()
1033
+ sage: X = G.reduced_groebner_bases()
1034
+ sage: len(X)
1035
+ 33
1036
+ sage: X[0]
1037
+ [z^15 - z, x - z^9, y - z^11]
1038
+ sage: X[0].ideal()
1039
+ Ideal (z^15 - z, x - z^9, y - z^11) of Multivariate Polynomial Ring in x, y, z over Rational Field
1040
+ sage: X[:5]
1041
+ [[z^15 - z, x - z^9, y - z^11],
1042
+ [y^2 - z^8, x - z^9, y*z^4 - z, -y + z^11],
1043
+ [y^3 - z^5, x - y^2*z, y^2*z^3 - y, y*z^4 - z, -y^2 + z^8],
1044
+ [y^4 - z^2, x - y^2*z, y^2*z^3 - y, y*z^4 - z, -y^3 + z^5],
1045
+ [y^9 - z, y^6*z - y, x - y^2*z, -y^4 + z^2]]
1046
+
1047
+ sage: # needs sage.rings.finite_rings
1048
+ sage: R3.<x,y,z> = PolynomialRing(GF(2477),3)
1049
+ sage: gf = R3.ideal([300*x^3 - y, y^2 - z, z^2 - 12]).groebner_fan()
1050
+ sage: gf.reduced_groebner_bases()
1051
+ [[z^2 - 12, y^2 - z, x^3 + 933*y],
1052
+ [y^4 - 12, x^3 + 933*y, -y^2 + z],
1053
+ [x^6 - 1062*z, z^2 - 12, -300*x^3 + y],
1054
+ [x^12 + 200, -300*x^3 + y, -828*x^6 + z]]
1055
+ """
1056
+ G = self._gfan_reduced_groebner_bases()
1057
+ if G.find(']') != -1:
1058
+ G = G.split(']')[1]
1059
+ G = G.replace('{{', '').replace('}}', '').split('},{')
1060
+ S = self.__ring
1061
+ return [ReducedGroebnerBasis(self, [S(f) for f in G[i].split(',')],
1062
+ G[i]) for i in range(len(G))]
1063
+
1064
+ @cached_method
1065
+ def _gfan_mod(self) -> str:
1066
+ """
1067
+ Return the extra options to the ``gfan`` command that are used
1068
+ by this object to account for working modulo a prime or in the
1069
+ presence of extra symmetries.
1070
+
1071
+ EXAMPLES::
1072
+
1073
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1074
+ sage: gf = R.ideal([x^3-y,y^3-x-1]).groebner_fan()
1075
+ sage: gf._gfan_mod()
1076
+ ''
1077
+ """
1078
+ mod = ''
1079
+ # p = self.characteristic()
1080
+ # if p:
1081
+ # mod += ' --mod %s' % p
1082
+ # else:
1083
+ # mod += ''
1084
+
1085
+ if self.__is_groebner_basis:
1086
+ mod += ' -g'
1087
+
1088
+ if self.__symmetry:
1089
+ mod += ' --symmetry'
1090
+
1091
+ return mod
1092
+
1093
+ def gfan(self, cmd='bases', I=None, format=None):
1094
+ r"""
1095
+ Return the ``gfan`` output as a string given an input ``cmd``.
1096
+
1097
+ The default is to produce the list of reduced Groebner bases
1098
+ in ``gfan`` format.
1099
+
1100
+ INPUT:
1101
+
1102
+ - ``cmd`` -- string (default: ``'bases'``); GFan command
1103
+ - ``I`` -- ideal (default: ``None``)
1104
+ - ``format`` -- boolean (default: ``None``); deprecated
1105
+
1106
+ EXAMPLES::
1107
+
1108
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1109
+ sage: gf = R.ideal([x^3-y,y^3-x-1]).groebner_fan()
1110
+ sage: gf.gfan()
1111
+ 'Q[x,y]\n{{\ny^9-1-y+3*y^3-3*y^6,\nx+1-y^3}\n,\n{\nx^3-y,\ny^3-1-x}\n,\n{\nx^9-1-x,\ny-x^3}\n}\n'
1112
+ """
1113
+ if format is not None:
1114
+ from sage.misc.superseded import deprecation
1115
+ deprecation(33468, 'argument `format` is ignored in the code: '
1116
+ 'it is now deprecated. Please update your code '
1117
+ 'without this argument as it will be removed in a later '
1118
+ 'version of SageMath.')
1119
+
1120
+ if I is None:
1121
+ I = self._gfan_ideal()
1122
+ # todo -- put something in here (?) when self.__symmetry isn't None...
1123
+ cmd += self._gfan_mod()
1124
+ s = gfan(I, cmd, verbose=self.__verbose)
1125
+ if s.strip() == '{':
1126
+ raise RuntimeError("Error running gfan command %s on %s" % (cmd, self))
1127
+ return s
1128
+
1129
+ def __iter__(self) -> Iterator:
1130
+ """
1131
+ Return an iterator for the reduced Groebner bases.
1132
+
1133
+ EXAMPLES::
1134
+
1135
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1136
+ sage: gf = R.ideal([x^3-y,y^3-x-1]).groebner_fan()
1137
+ sage: a = gf.__iter__()
1138
+ sage: next(a)
1139
+ [y^9 - 3*y^6 + 3*y^3 - y - 1, -y^3 + x + 1]
1140
+ """
1141
+ yield from self.reduced_groebner_bases()
1142
+
1143
+ def __getitem__(self, i):
1144
+ """
1145
+ Get a reduced groebner basis.
1146
+
1147
+ EXAMPLES::
1148
+
1149
+ sage: R4.<w1,w2,w3,w4> = PolynomialRing(QQ,4)
1150
+ sage: gf = R4.ideal([w1^2-w2,w2^3-1,2*w3-w4^2,w4^2-w1]).groebner_fan()
1151
+ sage: gf[0]
1152
+ [w4^12 - 1, -w4^4 + w2, -w4^2 + w1, -1/2*w4^2 + w3]
1153
+ """
1154
+ return self.reduced_groebner_bases()[i]
1155
+
1156
+ @cached_method
1157
+ def buchberger(self):
1158
+ """
1159
+ Return a lexicographic reduced Groebner basis for the ideal.
1160
+
1161
+ EXAMPLES::
1162
+
1163
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
1164
+ sage: G = R.ideal([x - z^3, y^2 - x + x^2 - z^3*x]).groebner_fan()
1165
+ sage: G.buchberger()
1166
+ [-z^3 + y^2, -z^3 + x]
1167
+ """
1168
+ B = self.gfan(cmd='buchberger')
1169
+ if B.find(']') != -1:
1170
+ B = B.split(']')[1]
1171
+ B = B.replace('}', '').replace('{', '')
1172
+ S = self.__ring
1173
+ return [S(f) for f in B.split(',')]
1174
+
1175
+ @cached_method
1176
+ def polyhedralfan(self):
1177
+ """
1178
+ Return a polyhedral fan object corresponding to the reduced
1179
+ Groebner bases.
1180
+
1181
+ EXAMPLES::
1182
+
1183
+ sage: R3.<x,y,z> = PolynomialRing(QQ,3)
1184
+ sage: gf = R3.ideal([x^8-y^4,y^4-z^2,z^2-1]).groebner_fan()
1185
+ sage: pf = gf.polyhedralfan()
1186
+ sage: pf.rays()
1187
+ [[0, 0, 1], [0, 1, 0], [1, 0, 0]]
1188
+ """
1189
+ f = self.gfan(cmd='topolyhedralfan', I=self._gfan_reduced_groebner_bases())
1190
+ return PolyhedralFan(f)
1191
+
1192
+ @cached_method
1193
+ def homogeneity_space(self):
1194
+ """
1195
+ Return the homogeneity space of a the list of polynomials that
1196
+ define this Groebner fan.
1197
+
1198
+ EXAMPLES::
1199
+
1200
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1201
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1202
+ sage: H = G.homogeneity_space()
1203
+ """
1204
+ return self.gfan(cmd='homogeneityspace')
1205
+
1206
+ def render(self, file=None, larger=False, shift=0, rgbcolor=(0, 0, 0),
1207
+ polyfill=True, scale_colors=True):
1208
+ """
1209
+ Render a Groebner fan as sage graphics or save as an xfig file.
1210
+
1211
+ More precisely, the output is a drawing of the Groebner fan
1212
+ intersected with a triangle. The corners of the triangle are
1213
+ (1,0,0) to the right, (0,1,0) to the left and (0,0,1) at the top.
1214
+ If there are more than three variables in the ring we extend these
1215
+ coordinates with zeros.
1216
+
1217
+ INPUT:
1218
+
1219
+ - ``file`` -- a filename if you prefer the output
1220
+ saved to a file; this will be in xfig format
1221
+
1222
+ - ``shift`` -- shift the positions of the variables in
1223
+ the drawing. For example, with shift=1, the corners will be b
1224
+ (right), c (left), and d (top). The shifting is done modulo the
1225
+ number of variables in the polynomial ring. The default is 0.
1226
+
1227
+ - ``larger`` -- boolean (default: ``False``); if ``True``, make
1228
+ the triangle larger so that the shape of the Groebner region
1229
+ appears. Affects the xfig file but probably not the sage graphics (?).
1230
+
1231
+ - ``rgbcolor`` -- this will not affect the saved xfig
1232
+ file, only the sage graphics produced
1233
+
1234
+ - ``polyfill`` -- whether or not to fill the cones with
1235
+ a color determined by the highest degree in each reduced Groebner
1236
+ basis for that cone
1237
+
1238
+ - ``scale_colors`` -- if ``True``, this will normalize
1239
+ color values to try to maximize the range
1240
+
1241
+ EXAMPLES::
1242
+
1243
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
1244
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x,z]).groebner_fan()
1245
+ sage: test_render = G.render() # needs sage.plot
1246
+
1247
+ ::
1248
+
1249
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
1250
+ sage: G = R.ideal([x^2*y - z, y^2*z - x, z^2*x - y]).groebner_fan()
1251
+ sage: test_render = G.render(larger=True) # needs sage.plot
1252
+
1253
+ TESTS:
1254
+
1255
+ Testing the case where the number of generators is < 3. Currently,
1256
+ this should raise a :exc:`NotImplementedError`.
1257
+
1258
+ ::
1259
+
1260
+ sage: R.<x,y> = PolynomialRing(QQ, 2)
1261
+ sage: R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan().render() # needs sage.plot
1262
+ Traceback (most recent call last):
1263
+ ...
1264
+ NotImplementedError
1265
+ """
1266
+ if polyfill is True:
1267
+ polyfill = max_degree
1268
+ S = self.__ring
1269
+ if S.ngens() < 3:
1270
+ print("For 2-D fan rendering the polynomial ring must have 3 variables (or more, which are ignored).")
1271
+ raise NotImplementedError
1272
+ cmd = 'render'
1273
+ if shift:
1274
+ cmd += ' --shiftVariables %s' % shift
1275
+ if larger:
1276
+ cmd += ' -L'
1277
+ s = self.gfan(cmd, I=self._gfan_reduced_groebner_bases().replace(' ', ','))
1278
+ if file is not None:
1279
+ with open(file, 'w') as f:
1280
+ f.write(s)
1281
+ sp = s.split('\n')
1282
+ sp2 = []
1283
+ for x in sp[9:]:
1284
+ xs = x.split(' ')
1285
+ y = []
1286
+ if x[0:3] != '2 3' and len(xs) > 1:
1287
+ y.extend(q for q in xs if q)
1288
+ sp2.append(y)
1289
+ sp3 = []
1290
+ for j in range(len(sp2)):
1291
+ temp = [[float(sp2[j][i]) / 1200.0,
1292
+ float(sp2[j][i + 1]) / 1200.0]
1293
+ for i in range(0, len(sp2[j]) - 1, 2)]
1294
+ sp3.append(temp)
1295
+ r_lines = Graphics()
1296
+ for x in sp3:
1297
+ r_lines = r_lines + line(x, rgbcolor=rgbcolor)
1298
+ if polyfill:
1299
+ vals = [polyfill(q) for q in self.reduced_groebner_bases()]
1300
+ if isinstance(vals[0], (list, tuple)):
1301
+ if scale_colors:
1302
+ vmins = [min([q[i] for q in vals]) for i in (0, 1, 2)]
1303
+ vmaxs = [max([q[i] for q in vals]) for i in (0, 1, 2)]
1304
+ for i in (0, 1, 2):
1305
+ if vmaxs[i] == vmins[i]:
1306
+ vmaxs[i] = vmins[i] + .01
1307
+ for index, sp in enumerate(sp3):
1308
+ col = [1 - (vals[index][i] - vmins[i]) / (vmaxs[i] - vmins[i]) for i in (0, 1, 2)]
1309
+ r_lines += polygon(sp, rgbcolor=col)
1310
+ else:
1311
+ for index, sp in enumerate(sp3):
1312
+ r_lines += polygon(sp, rgbcolor=vals[index])
1313
+ elif scale_colors:
1314
+ vmin = min(vals)
1315
+ vmax = max(vals)
1316
+ if vmin == vmax:
1317
+ vmax = vmin + .01
1318
+ for index, sp in enumerate(sp3):
1319
+ r_lines += polygon(sp, hue=.1 + .6 * (vals[index] - vmin) / (vmax - vmin))
1320
+ else:
1321
+ for index, sp in enumerate(sp3):
1322
+ r_lines += polygon(sp, hue=vals[index])
1323
+ return r_lines
1324
+
1325
+ def _cone_to_ieq(self, facet_list):
1326
+ """
1327
+ A simple utility function for converting a facet normal to an
1328
+ inequality form.
1329
+
1330
+ EXAMPLES::
1331
+
1332
+ sage: R.<x,y> = PolynomialRing(QQ,2) # dummy stuff to get a gfan object
1333
+ sage: gf = R.ideal([x^2+y,y^2]).groebner_fan()
1334
+ sage: gf._cone_to_ieq([[1,2,3,4]])
1335
+ [[0, 1, 2, 3, 4]]
1336
+ """
1337
+ return [[0] + q for q in facet_list]
1338
+
1339
+ def _embed_tetra(self, fpoint):
1340
+ """
1341
+ Take a 4-d vector and projects it onto the plane perpendicular to
1342
+ (1,1,1,1). Stretches by a factor of 2 as well, since this is only
1343
+ for graphical display purposes.
1344
+
1345
+ INPUT:
1346
+
1347
+ - ``fpoint`` -- list of four numbers
1348
+
1349
+ EXAMPLES::
1350
+
1351
+ sage: R.<x> = PolynomialRing(QQ,1) # dummy stuff to get a gfan object
1352
+ sage: gf = R.ideal([x^2]).groebner_fan()
1353
+ sage: gf._embed_tetra([1/2,1/2,1/2,1/2])
1354
+ [0, 0, 0]
1355
+ """
1356
+ v1 = [1, 1, -1, -1]
1357
+ v2 = [1, -1, 1, -1]
1358
+ v3 = [-1, 1, 1, -1]
1359
+ x1 = sum([fpoint[ind] * v1[ind] for ind in range(4)])
1360
+ x2 = sum([fpoint[ind] * v2[ind] for ind in range(4)])
1361
+ x3 = sum([fpoint[ind] * v3[ind] for ind in range(4)])
1362
+ return [x1, x2, x3]
1363
+
1364
+ def _4d_to_3d(self, polyhedral_data):
1365
+ """
1366
+ A utility function that takes a list of 4d polytopes, projects them
1367
+ to 3d, and returns a list of edges.
1368
+
1369
+ INPUT:
1370
+
1371
+ - ``polyhedral_data`` -- an object with 4d vertex and adjacency
1372
+ information
1373
+
1374
+ OUTPUT:
1375
+
1376
+ - ``edges`` -- list of edges in 3d; each list item is a pair of
1377
+ points
1378
+
1379
+ EXAMPLES::
1380
+
1381
+ sage: R4.<w,x,y,z> = PolynomialRing(QQ,4)
1382
+ sage: gf = R4.ideal([w^2-x,x^2-y,y^2-z,z^2-1]).groebner_fan()
1383
+ sage: g_cone = gf[0].groebner_cone()
1384
+ sage: g_cone_facets = g_cone.facets()
1385
+ sage: g_cone_ieqs = gf._cone_to_ieq(g_cone_facets)
1386
+ sage: cone_data = Polyhedron(ieqs = g_cone_ieqs, eqns = [[1,-1,-1,-1,-1]])
1387
+ sage: cone_lines = gf._4d_to_3d(cone_data)
1388
+ sage: cone_lines
1389
+ [[[1, 1, -1], [1, -1/3, 1/3]],
1390
+ [[1, 1, -1], [-1/7, 3/7, 5/7]],
1391
+ [[1, 1, -1], [-3/5, -1/3, -1/5]],
1392
+ [[1, -1/3, 1/3], [-1/7, 3/7, 5/7]],
1393
+ [[1, -1/3, 1/3], [-3/5, -1/3, -1/5]],
1394
+ [[-1/7, 3/7, 5/7], [-3/5, -1/3, -1/5]]]
1395
+ """
1396
+ fpoints = polyhedral_data.vertices()
1397
+ tpoints = [self._embed_tetra(q) for q in fpoints]
1398
+ edges = []
1399
+ for vertex in polyhedral_data.vertices():
1400
+ i = vertex.index()
1401
+ for adjacent_vertex in vertex.adjacent():
1402
+ j = adjacent_vertex.index()
1403
+ if j > i:
1404
+ try:
1405
+ edges.append([tpoints[i], tpoints[j]])
1406
+ except Exception:
1407
+ print('tpoints: ' + str(tpoints))
1408
+ print('fpoints: ' + str(fpoints))
1409
+ print(adjacent_vertex)
1410
+ print(polyhedral_data.ieqs())
1411
+ raise RuntimeError
1412
+ return edges
1413
+
1414
+ def render3d(self, verbose=False):
1415
+ """
1416
+ For a Groebner fan of an ideal in a ring with four variables, this
1417
+ function intersects the fan with the standard simplex perpendicular
1418
+ to (1,1,1,1), creating a 3d polytope, which is then projected into
1419
+ 3 dimensions. The edges of this projected polytope are returned as
1420
+ lines.
1421
+
1422
+ EXAMPLES::
1423
+
1424
+ sage: R4.<w,x,y,z> = PolynomialRing(QQ,4)
1425
+ sage: gf = R4.ideal([w^2-x,x^2-y,y^2-z,z^2-x]).groebner_fan()
1426
+ sage: three_d = gf.render3d() # needs sage.plot
1427
+
1428
+ TESTS:
1429
+
1430
+ Now test the case where the number of generators is not 4. Currently,
1431
+ this should raise a :exc:`NotImplementedError` error.
1432
+
1433
+ ::
1434
+
1435
+ sage: P.<a,b,c> = PolynomialRing(QQ, 3, order='lex')
1436
+ sage: sage.rings.ideal.Katsura(P, 3).groebner_fan().render3d() # needs sage.plot
1437
+ Traceback (most recent call last):
1438
+ ...
1439
+ NotImplementedError
1440
+ """
1441
+ S = self.__ring
1442
+ if S.ngens() != 4:
1443
+ print("For 3-D fan rendering the polynomial ring must have 4 variables")
1444
+ raise NotImplementedError
1445
+ g_cones = [q.groebner_cone() for q in self.reduced_groebner_bases()]
1446
+ g_cones_facets = [q.facets() for q in g_cones]
1447
+ g_cones_ieqs = [self._cone_to_ieq(q) for q in g_cones_facets]
1448
+ # Now the cones are intersected with a plane:
1449
+ cone_info = [Polyhedron(ieqs=q, eqns=[[1, -1, -1, -1, -1]])
1450
+ for q in g_cones_ieqs]
1451
+ # This is really just for debugging
1452
+ if verbose:
1453
+ for x in cone_info:
1454
+ print(x.inequalities() + ([1, 1, 0, 0, 0], [1, 0, 1, 0, 0],
1455
+ [1, 0, 0, 1, 0], [1, 0, 0, 0, 1]))
1456
+ print(x.equations())
1457
+ print()
1458
+ cone_info = [Polyhedron(ieqs=x.inequalities() +
1459
+ ([1, 1, 0, 0, 0], [1, 0, 1, 0, 0],
1460
+ [1, 0, 0, 1, 0], [1, 0, 0, 0, 1]),
1461
+ eqns=x.equations()) for x in cone_info]
1462
+ all_lines = []
1463
+ for cone_data in cone_info:
1464
+ try:
1465
+ cone_lines = self._4d_to_3d(cone_data)
1466
+ except Exception:
1467
+ print(cone_data._rays)
1468
+ raise RuntimeError
1469
+ all_lines.extend(a_line for a_line in cone_lines)
1470
+ return sum([line3d(a_line) for a_line in all_lines])
1471
+
1472
+ @cached_method
1473
+ def _gfan_stats(self) -> dict:
1474
+ """
1475
+ Return various statistics about this Groebner fan.
1476
+
1477
+ EXAMPLES::
1478
+
1479
+ sage: R.<x,y> = PolynomialRing(QQ)
1480
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1481
+ sage: G._gfan_stats()
1482
+ {'Dimension of homogeneity space': 0,
1483
+ 'Maximal number of polynomials in Groebner basis': 3,
1484
+ 'Maximal number of terms in Groebner basis': 6,
1485
+ 'Maximal total degree of a Groebner basis': 4,
1486
+ 'Minimal total degree of a Groebner basis': 2,
1487
+ 'Number of reduced Groebner bases': 3,
1488
+ 'Number of variables': 2}
1489
+ """
1490
+ s = self.gfan(cmd='stats',
1491
+ I=self._gfan_reduced_groebner_bases().replace(' ', ','))
1492
+ d = {}
1493
+ for v in s.split('\n'):
1494
+ if v:
1495
+ a, b = v.split(':')
1496
+ d[a] = ZZ(b)
1497
+ return d
1498
+
1499
+ def dimension_of_homogeneity_space(self):
1500
+ """
1501
+ Return the dimension of the homogeneity space.
1502
+
1503
+ EXAMPLES::
1504
+
1505
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1506
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1507
+ sage: G.dimension_of_homogeneity_space()
1508
+ 0
1509
+ """
1510
+ return self._gfan_stats()['Dimension of homogeneity space']
1511
+
1512
+ def maximal_total_degree_of_a_groebner_basis(self):
1513
+ """
1514
+ Return the maximal total degree of any Groebner basis.
1515
+
1516
+ EXAMPLES::
1517
+
1518
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1519
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1520
+ sage: G.maximal_total_degree_of_a_groebner_basis()
1521
+ 4
1522
+ """
1523
+ return self._gfan_stats()['Maximal total degree of a Groebner basis']
1524
+
1525
+ def minimal_total_degree_of_a_groebner_basis(self):
1526
+ """
1527
+ Return the minimal total degree of any Groebner basis.
1528
+
1529
+ EXAMPLES::
1530
+
1531
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1532
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1533
+ sage: G.minimal_total_degree_of_a_groebner_basis()
1534
+ 2
1535
+ """
1536
+ return self._gfan_stats()['Minimal total degree of a Groebner basis']
1537
+
1538
+ def number_of_reduced_groebner_bases(self):
1539
+ """
1540
+ Return the number of reduced Groebner bases.
1541
+
1542
+ EXAMPLES::
1543
+
1544
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1545
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1546
+ sage: G.number_of_reduced_groebner_bases()
1547
+ 3
1548
+ """
1549
+ return self._gfan_stats()['Number of reduced Groebner bases']
1550
+
1551
+ def number_of_variables(self):
1552
+ """
1553
+ Return the number of variables.
1554
+
1555
+ EXAMPLES::
1556
+
1557
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1558
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1559
+ sage: G.number_of_variables()
1560
+ 2
1561
+
1562
+ ::
1563
+
1564
+ sage: R = PolynomialRing(QQ,'x',10)
1565
+ sage: R.inject_variables(globals())
1566
+ Defining x0, x1, x2, x3, x4, x5, x6, x7, x8, x9
1567
+ sage: G = ideal([x0 - x9, sum(R.gens())]).groebner_fan()
1568
+ sage: G.number_of_variables()
1569
+ 10
1570
+ """
1571
+ return self.__ring.ngens()
1572
+
1573
+ @cached_method
1574
+ def tropical_basis(self, check=True, verbose=False):
1575
+ """
1576
+ Return a tropical basis for the tropical curve associated to this
1577
+ ideal.
1578
+
1579
+ INPUT:
1580
+
1581
+ - ``check`` -- boolean (default: ``True``); if ``True`` raises a
1582
+ :exc:`ValueError` exception if this ideal does not define a tropical
1583
+ curve (i.e., the condition that R/I has dimension equal to 1 + the
1584
+ dimension of the homogeneity space is not satisfied)
1585
+
1586
+ EXAMPLES::
1587
+
1588
+ sage: R.<x,y,z> = PolynomialRing(QQ,3, order='lex')
1589
+ sage: G = R.ideal([y^3-3*x^2, z^3-x-y-2*y^3+2*x^2]).groebner_fan()
1590
+ sage: G
1591
+ Groebner fan of the ideal:
1592
+ Ideal (-3*x^2 + y^3, 2*x^2 - x - 2*y^3 - y + z^3) of Multivariate Polynomial Ring in x, y, z over Rational Field
1593
+ sage: G.tropical_basis() # needs sage.libs.singular
1594
+ [-3*x^2 + y^3, 2*x^2 - x - 2*y^3 - y + z^3, 3/4*x + y^3 + 3/4*y - 3/4*z^3]
1595
+ """
1596
+ cmd = 'tropicalbasis'
1597
+
1598
+ I = self.ideal()
1599
+ if not I.is_homogeneous():
1600
+ cmd += ' -h'
1601
+ if check:
1602
+ if I.dimension() != 1 + self.dimension_of_homogeneity_space():
1603
+ raise ValueError("The ideal does not define a tropical curve.")
1604
+
1605
+ B = self.gfan(cmd)
1606
+ if B.find(']') != -1:
1607
+ B = B.split(']')[1]
1608
+ S = self.__ring
1609
+ B = B.replace('\n', '')
1610
+ B = B.replace('{', '').replace('}', '').split(',')
1611
+ if verbose:
1612
+ print(S, B)
1613
+ return [S(f) for f in B]
1614
+
1615
+ def interactive(self, *args, **kwds):
1616
+ """
1617
+ See the documentation for self[0].interactive().
1618
+
1619
+ This does not work with the notebook.
1620
+
1621
+ EXAMPLES::
1622
+
1623
+ sage: print("This is not easily doc-testable; please write a good one!")
1624
+ This is not easily doc-testable; please write a good one!
1625
+ """
1626
+ self[0].interactive(*args, **kwds)
1627
+
1628
+ @cached_method
1629
+ def tropical_intersection(self, parameters=None, symmetry_generators=None,
1630
+ *args, **kwds):
1631
+ """
1632
+ Return information about the tropical intersection of the
1633
+ polynomials defining the ideal.
1634
+
1635
+ This is the common refinement of the outward-pointing normal
1636
+ fans of the Newton polytopes of the generators of the
1637
+ ideal. Note that some people use the inward-pointing normal
1638
+ fans.
1639
+
1640
+ INPUT:
1641
+
1642
+ - ``parameters`` -- (optional) tuple of variables to be
1643
+ considered as parameters
1644
+ - ``symmetry_generators`` -- (optional) generators of the symmetry group
1645
+
1646
+ OUTPUT: a TropicalPrevariety object
1647
+
1648
+ EXAMPLES::
1649
+
1650
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
1651
+ sage: I = R.ideal(x*z + 6*y*z - z^2, x*y + 6*x*z + y*z - z^2, y^2 + x*z + y*z)
1652
+ sage: gf = I.groebner_fan()
1653
+ sage: pf = gf.tropical_intersection()
1654
+ sage: pf.rays()
1655
+ [[-2, 1, 1]]
1656
+
1657
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
1658
+ sage: f1 = x*y*z - 1
1659
+ sage: f2 = f1*(x^2 + y^2 + z^2)
1660
+ sage: f3 = f2*(x + y + z - 1)
1661
+ sage: I = R.ideal([f1,f2,f3])
1662
+ sage: gf = I.groebner_fan()
1663
+ sage: pf = gf.tropical_intersection(symmetry_generators = '(1,2,0),(1,0,2)')
1664
+ sage: pf.rays()
1665
+ [[-2, 1, 1], [1, -2, 1], [1, 1, -2]]
1666
+
1667
+ sage: R.<x,y,z> = QQ[]
1668
+ sage: I = R.ideal([(x+y+z)^2-1,(x+y+z)-x,(x+y+z)-3])
1669
+ sage: GF = I.groebner_fan()
1670
+ sage: TI = GF.tropical_intersection()
1671
+ sage: TI.rays()
1672
+ [[-1, 0, 0], [0, -1, -1], [1, 1, 1]]
1673
+ sage: GF = I.groebner_fan()
1674
+ sage: TI = GF.tropical_intersection(parameters=(y,))
1675
+ sage: TI.rays()
1676
+ [[-1, 0, 0]]
1677
+ """
1678
+ if parameters is None:
1679
+ parameters = []
1680
+ if symmetry_generators is None:
1681
+ symmetry_generators = []
1682
+ cmd = 'tropicalintersection'
1683
+ id_str = self._gfan_ideal()
1684
+ if parameters:
1685
+ allvars = self.ring().gens()
1686
+ truevars = [q for q in allvars if q not in parameters]
1687
+ base_ring = self.ring().base_ring()
1688
+ new_ring = PolynomialRing(base_ring, len(truevars),
1689
+ ",".join(str(q) for q in truevars))
1690
+ old_polys = self.ideal().gens()
1691
+ new_polys = []
1692
+ sub = {v: 1 for v in parameters}
1693
+ for apoly in old_polys:
1694
+ mons = apoly.monomials()
1695
+ mons = [m.subs(sub) for m in mons]
1696
+ new_polys.append(sum(mons))
1697
+ id_str = ideal_to_gfan_format(new_ring, new_polys)
1698
+ if symmetry_generators:
1699
+ cmd = cmd + ' --symmetryExploit'
1700
+ id_str = id_str + '{' + symmetry_generators + '}'
1701
+ f = self.gfan(cmd=cmd, I=id_str)
1702
+ pf = TropicalPrevariety(f, self.ideal().gens(), self.ring(),
1703
+ parameters=parameters)
1704
+ pf._gfan_output = f
1705
+ return pf
1706
+
1707
+ def mixed_volume(self):
1708
+ """
1709
+ Return the mixed volume of the generators of this ideal.
1710
+
1711
+ This is not really an ideal property, it can depend on the
1712
+ generators used.
1713
+
1714
+ The generators must give a square system (as many polynomials
1715
+ as variables).
1716
+
1717
+ EXAMPLES::
1718
+
1719
+ sage: R.<x,y,z> = QQ[]
1720
+ sage: example_ideal = R.ideal([x^2-y-1,y^2-z-1,z^2-x-1])
1721
+ sage: gf = example_ideal.groebner_fan()
1722
+ sage: mv = gf.mixed_volume()
1723
+ sage: mv
1724
+ 8
1725
+
1726
+ sage: R2.<x,y> = QQ[]
1727
+ sage: g1 = 1 - x + x^7*y^3 + 2*x^8*y^4
1728
+ sage: g2 = 2 + y + 3*x^7*y^3 + x^8*y^4
1729
+ sage: example2 = R2.ideal([g1,g2])
1730
+ sage: example2.groebner_fan().mixed_volume()
1731
+ 15
1732
+ """
1733
+ if len(self.ring().variable_names()) != self.ideal().ngens():
1734
+ raise ValueError('not a square system')
1735
+ return Integer(self.gfan(cmd='mixedvolume'))
1736
+
1737
+
1738
+ class ReducedGroebnerBasis(SageObject, list):
1739
+ def __init__(self, groebner_fan, gens, gfan_gens) -> None:
1740
+ """
1741
+ A class for representing reduced Groebner bases as produced by
1742
+ ``gfan``.
1743
+
1744
+ INPUT:
1745
+
1746
+ - ``groebner_fan`` -- a GroebnerFan object from an ideal
1747
+
1748
+ - ``gens`` -- the generators of the ideal
1749
+
1750
+ - ``gfan_gens`` -- the generators as a gfan string
1751
+
1752
+ EXAMPLES::
1753
+
1754
+ sage: R.<a,b> = PolynomialRing(QQ,2)
1755
+ sage: gf = R.ideal([a^2-b^2,b-a-1]).groebner_fan()
1756
+ sage: from sage.rings.polynomial.groebner_fan import ReducedGroebnerBasis
1757
+ sage: ReducedGroebnerBasis(gf,gf[0],gf[0]._gfan_gens())
1758
+ [b - 1/2, a + 1/2]
1759
+ """
1760
+ self.__groebner_fan = groebner_fan
1761
+ list.__init__(self, gens)
1762
+ self.__gfan_gens = '{' + gfan_gens.replace(' ', ',') + '}'
1763
+ self.__ring = groebner_fan._gfan_ring()
1764
+
1765
+ def _repr_(self) -> str:
1766
+ """
1767
+ Return the reduced Groebner basis as a string.
1768
+
1769
+ EXAMPLES::
1770
+
1771
+ sage: R.<z1,zz1> = PolynomialRing(QQ,2)
1772
+ sage: gf = R.ideal([z1^2*zz1-1,zz1-2]).groebner_fan()
1773
+ sage: rgb1 = gf.reduced_groebner_bases()[0]
1774
+ sage: rgb1 # indirect doctest
1775
+ [zz1 - 2, z1^2 - 1/2]
1776
+ """
1777
+ return list.__repr__(self)
1778
+
1779
+ def _gfan_gens(self):
1780
+ """
1781
+ Return the reduced Groebner basis as a string in ``gfan`` format.
1782
+
1783
+ EXAMPLES::
1784
+
1785
+ sage: R.<z1,zz1> = PolynomialRing(QQ,2)
1786
+ sage: gf = R.ideal([z1^2*zz1-1,zz1-2]).groebner_fan()
1787
+ sage: rgb1 = gf.reduced_groebner_bases()[0]
1788
+ sage: rgb1._gfan_gens()
1789
+ '{zz1-2,z1^2-1/2}'
1790
+ """
1791
+ return self.__gfan_gens
1792
+
1793
+ def _gfan(self):
1794
+ """
1795
+ Return a description of the Groebner fan this basis was derived
1796
+ from.
1797
+
1798
+ EXAMPLES::
1799
+
1800
+ sage: R.<z1,zz1> = PolynomialRing(QQ,2)
1801
+ sage: gf = R.ideal([z1^2*zz1-1,zz1-2]).groebner_fan()
1802
+ sage: rgb1 = gf.reduced_groebner_bases()[0]
1803
+ sage: rgb1._gfan()
1804
+ Groebner fan of the ideal:
1805
+ Ideal (z1^2*zz1 - 1, zz1 - 2) of Multivariate Polynomial Ring in z1, zz1 over Rational Field
1806
+ """
1807
+ return self.__groebner_fan
1808
+
1809
+ def interactive(self, latex=False, flippable=False, wall=False,
1810
+ inequalities=False, weight=False):
1811
+ """
1812
+ Do an interactive walk of the Groebner fan starting at this reduced
1813
+ Groebner basis.
1814
+
1815
+ EXAMPLES::
1816
+
1817
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1818
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1819
+ sage: G[0].interactive() # not tested
1820
+ Initializing gfan interactive mode
1821
+ *********************************************
1822
+ * Press control-C to return to Sage *
1823
+ *********************************************
1824
+ ....
1825
+ """
1826
+ cmd = 'gfan_interactive'
1827
+ if latex:
1828
+ cmd += ' -L'
1829
+ if flippable:
1830
+ cmd += ' -f'
1831
+ if wall:
1832
+ cmd += ' -w'
1833
+ if inequalities:
1834
+ cmd += ' -i'
1835
+ if weight:
1836
+ cmd += ' -W'
1837
+ cmd += self.__groebner_fan._gfan_mod()
1838
+ E = pexpect.spawn(cmd)
1839
+ print("Initializing gfan interactive mode")
1840
+ # E.sendline(self._gfan_ideal())
1841
+ E.sendline(self.__gfan_gens)
1842
+ print("*" * 45)
1843
+ print("* Press control-C to return to Sage *")
1844
+ print("*" * 45)
1845
+ try:
1846
+ E.interact()
1847
+ except OSError:
1848
+ print("Returning to Sage.")
1849
+
1850
+ def groebner_cone(self, restrict=False):
1851
+ """
1852
+ Return defining inequalities for the full-dimensional Groebner cone
1853
+ associated to this marked minimal reduced Groebner basis.
1854
+
1855
+ INPUT:
1856
+
1857
+ - ``restrict`` -- boolean (default: ``False``); if ``True``, add
1858
+ an inequality for each coordinate, so that the cone is restricted
1859
+ to the positive orthant
1860
+
1861
+ OUTPUT: tuple of integer vectors
1862
+
1863
+ EXAMPLES::
1864
+
1865
+ sage: R.<x,y> = PolynomialRing(QQ,2)
1866
+ sage: G = R.ideal([y^3 - x^2, y^2 - 13*x]).groebner_fan()
1867
+ sage: poly_cone = G[1].groebner_cone()
1868
+ sage: poly_cone.facets()
1869
+ [[-1, 2], [1, -1]]
1870
+ sage: [g.groebner_cone().facets() for g in G]
1871
+ [[[0, 1], [1, -2]], [[-1, 2], [1, -1]], [[-1, 1], [1, 0]]]
1872
+ sage: G[1].groebner_cone(restrict=True).facets()
1873
+ [[-1, 2], [1, -1]]
1874
+ """
1875
+ try:
1876
+ return self.__groebner_cone[restrict]
1877
+ except AttributeError:
1878
+ self.__groebner_cone = {}
1879
+ except KeyError:
1880
+ pass
1881
+ cmd = 'groebnercone'
1882
+ if restrict:
1883
+ cmd += ' --restrict'
1884
+ gf = self.__groebner_fan
1885
+ c = gf.gfan(cmd=cmd, I=self.__ring + self.__gfan_gens)
1886
+ return PolyhedralCone(c)
1887
+
1888
+ def ideal(self):
1889
+ """
1890
+ Return the ideal generated by this basis.
1891
+
1892
+ EXAMPLES::
1893
+
1894
+ sage: R.<x,y,z> = PolynomialRing(QQ,3)
1895
+ sage: G = R.ideal([x - z^3, y^2 - 13*x]).groebner_fan()
1896
+ sage: G[0].ideal()
1897
+ Ideal (-13*z^3 + y^2, -z^3 + x) of Multivariate Polynomial Ring in x, y, z over Rational Field
1898
+ """
1899
+ return self.__groebner_fan.ring().ideal(self)