File: SnapPea.py

package info (click to toggle)
snappea 3.0d3-23
  • links: PTS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 5,900 kB
  • ctags: 3,582
  • sloc: ansic: 33,469; sh: 8,565; python: 7,623; makefile: 242
file content (576 lines) | stat: -rw-r--r-- 17,766 bytes parent folder | download | duplicates (8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
#	SnapPea.py
#
#	The Python interface to the SnapPea kernel consists of two parts.
#
#		SnapPea.py (this file) defines a set of objects (Triangulation,
#			AbelianGroup, etc.) purely in Python.
#
#		SnapPeaC.c (in a different file) implements SnapPea.py's methods
#			as a set of wrappers to the standard SnapPea kernel functions,
#			which are written in C.

import SnapPeaC

False = 0
True  = 1


#	Keep track of when memory is released.
def VerifyMyMallocUsage():
	return SnapPeaC.verify_my_malloc_usage()
	

class Triangulation:
	
	def __init__(self, spec, orientable = 1, index = 0):
		#	Triangulation('file_name') reads a file from disk.
		#	Triangulation(num_tet (=5,6,7), orientable (=0(false),1(true)), index)
		#		reads a cusped census manifold.
		#	Triangulation((long) (Trianguation *)) accepts a pointer
		#		to an existing SnapPea Triangulation.
		if spec in range(5,8):
			self.triangulation = SnapPeaC.get_cusped_census_manifold(spec, orientable, index)
		elif type(spec) == type(''):
			self.triangulation = SnapPeaC.get_triangulation(spec)
		else:
			self.triangulation = spec
	
	def __del__(self):
		#	Let the SnapPea kernel free its private representation
		#	of a Triangulation.
		SnapPeaC.free_triangulation(self.triangulation)
	
	def __repr__(self):

		s = '\n'
		s = s + 'name:      %s' % SnapPeaC.get_triangulation_name(self.triangulation) + '\n'
		s = s + 'volume:    %f' % SnapPeaC.volume(self.triangulation)				 + '\n'
		s = s + 'homology:  %s' % self.homology()									 + '\n'
		s = s + 'Dehn fillings:\n'
		for i in range(SnapPeaC.get_num_cusps(self.triangulation)):
			s = s + '%3i  ' % i
			if SnapPeaC.get_cusp_is_complete(self.triangulation, i):
				s = s + '    complete'
			else:
				s = s + '%f ' % SnapPeaC.get_cusp_m(self.triangulation, i)
				if SnapPeaC.get_cusp_is_orientable(self.triangulation, i):
					s = s + '%f ' % SnapPeaC.get_cusp_l(self.triangulation, i)
			s = s + '\n'
		
		return s
	
	def save(self, file_name):
		if len(file_name) > 0:
			SnapPeaC.set_triangulation_name(self.triangulation, file_name)
		SnapPeaC.save_triangulation(self.triangulation, file_name)
	
	def clone(self):
		return Triangulation(SnapPeaC.copy_triangulation(self.triangulation))
	
	def get_name(self):
		return SnapPeaC.get_triangulation_name(self.triangulation)
	
	def set_name(self, name):
		SnapPeaC.set_triangulation_name(self.triangulation, name)
		
	def get_triangulation_is_orientable(self):
		return SnapPeaC.get_triangulation_is_orientable(self.triangulation)
	
	def get_solution_type(self):
		return SnapPeaC.get_solution_type(self.triangulation)
	
	def get_num_tetrahedra(self):
		return SnapPeaC.get_num_tetrahedra(self.triangulation)
	
	def get_num_cusps(self):
		return SnapPeaC.get_num_cusps(self.triangulation)
	
	def get_cusp_is_orientable(self, i):
		return SnapPeaC.get_cusp_is_orientable(self.triangulation, i)
	
	def get_cusp_is_complete(self, i):
		return SnapPeaC.get_cusp_is_complete(self.triangulation, i)
	
	def get_cusp_m(self, i):
		return SnapPeaC.get_cusp_m(self.triangulation, i)
	
	def get_cusp_l(self, i):
		return SnapPeaC.get_cusp_l(self.triangulation, i)
	
	def set_cusp(self, index, m = 0, l = 0, recompute = 1):
	
		#	set_cusp(i)
		#		makes cusp i complete.
		#
		#	set_cusp(i, m, l)
		#		does (m,l) Dehn filling on cusp i.
		#		(As a special case, set_cusp(i, 0, 0) is equivalent
		#		to set_cusp(i), and makes the cusp complete.)
		#
		#	set_cusp(i, m, l, 0)
		#		sets the cusp coefficients, but doesn't attempt
		#		to find the hyperbolic structure.
		#		(set_cusp(i, m, l, 1) is equivalent to set_cusp(i, m, l).)
		
		if index in range(SnapPeaC.get_num_cusps(self.triangulation)):
			SnapPeaC.set_cusp_info(self.triangulation, index, m, l, recompute)
		else:
			return 'cusps are numbered 0 through %i\n' % SnapPeaC.get_num_cusps(self.triangulation)
	
	def cusp_is_fillable(self, index):
		return SnapPeaC.cusp_is_fillable(self.triangulation, index)
	
	def remove_Dehn_fillings(self):
		return SnapPeaC.remove_Dehn_fillings(self.triangulation)
	
	def tet_shapes(self, use_fixed_coordinates):
		return SnapPeaC.tet_shapes(self.triangulation, use_fixed_coordinates)
	
	def volume(self):
		return SnapPeaC.volume(self.triangulation)
	
	def homology(self):
		#	If the homology couldn't be computed
		#	(perhaps because some Dehn filling coefficients
		#	aren't integers) return None.
		#	Otherwise return the corresponding AbelianGroup.
		#	Note:  An empty coefficient list is not None,
		#	and will generate an AbelianGroup with
		#	no torsion coefficients.
		theTorsionCoefficients = SnapPeaC.homology(self.triangulation)
		if theTorsionCoefficients == None:
			return None
		else:
			return AbelianGroup(theTorsionCoefficients)
	
	def fundamental_group(	self,
							simplify_flag = 1,
							fillings_affect_generators_flag = 1,
							minimize_num_generators_flag = 0):
		return FundamentalGroup(SnapPeaC.fundamental_group(
									self.triangulation,
									simplify_flag,
									fillings_affect_generators_flag,
									minimize_num_generators_flag))

	def fill_cusp(self, index):
		if index not in range(self.get_num_cusps()):
			return 'cusps are numbered 0 through %i\n' % self.get_num_cusps()
		elif self.get_num_cusps() == 1:
			print 'The triangulation must retain at least one cusp.'
		elif not self.cusp_is_fillable(index):
			print 'To permanently fill a cusp, the Dehn filling coefficients must be relatively prime integers.'
		else:
			theFilledTriangulation = SnapPeaC.fill_cusp(self.triangulation, index)
			if (theFilledTriangulation != self.triangulation):
				SnapPeaC.free_triangulation(self.triangulation)
				self.triangulation = theFilledTriangulation
	
	def get_drillable_curves(self, max_segments = 6):
		return SnapPeaC.get_drillable_curves(self.triangulation, max_segments)
	
	def drill_curve(self, index, max_segments = 6):
		theDrilledTriangulation = SnapPeaC.drill_curve(self.triangulation, index, max_segments)
		if (theDrilledTriangulation != 0):
			SnapPeaC.free_triangulation(self.triangulation)
			self.triangulation = theDrilledTriangulation
	
	def get_normal_surfaces(self):
		return SnapPeaC.get_normal_surfaces(self.triangulation)
	
	def split_on_normal_surface(self, index):
		#	SnapPeaC produces bare SnapPea Triangulations.
		#	Convert them to Python Triangulation objects.
		theBareTriangulations = SnapPeaC.split_along_normal_surface(self.triangulation, index)
		theTriangulationObjects = []
		for theBareTriangulation in theBareTriangulations:
			theTriangulationObjects.append(Triangulation(theBareTriangulation))
		return theTriangulationObjects
	
	def core_geodesic(self, index):
		return SnapPeaC.core_geodesic(self.triangulation, index)
	
	def shortest_curves_become_meridians(self):
		SnapPeaC.shortest_curves_become_meridians(self.triangulation)
	
	def current_fillings_become_meridians(self):
		SnapPeaC.current_fillings_become_meridians(self.triangulation)
	
	def simplify(self):
		SnapPeaC.basic_simplification(self.triangulation)
	
	def randomize(self):
		SnapPeaC.randomize_triangulation(self.triangulation)
	
	def reflect(self):
		SnapPeaC.reorient(self.triangulation)
	
	def canonize(self):
		SnapPeaC.proto_canonize(self.triangulation)
	
	def is_canonical_triangulation(self):
		return SnapPeaC.is_canonical_triangulation(self.triangulation)
	
	def symmetry_group(self):

		theRawResults = SnapPeaC.symmetry_group(self.triangulation)

		thePackagedResults = {}
		
		if theRawResults[0] == 0:
			thePackagedResults['manifold'] = None
		else:
			thePackagedResults['manifold'] = SymmetryGroup(theRawResults[0], theRawResults[3])
		
		if theRawResults[1] == 0:
			thePackagedResults['link'] = None
		else:
			thePackagedResults['link'] = SymmetryGroup(theRawResults[1], theRawResults[3])
		
		if theRawResults[2] == 0:
			thePackagedResults['symmetric triangulation'] = None
		else:
			thePackagedResults['symmetric triangulation'] = Triangulation(theRawResults[2])

		return thePackagedResults
	
	def Dirichlet(self, centroid_at_origin=1, maximize_inj_radius=0,
				displacement=(0,0,0)):
		return DirichletDomain(SnapPeaC.Dirichlet(	self.triangulation,
													centroid_at_origin,
													maximize_inj_radius,
													displacement[0],
													displacement[1],
													displacement[2]))


class CuspedCensus:

	def __init__(self, num_tetrahedra = 5, orientable = 'o'):
	
		self.num_tetrahedra	= num_tetrahedra
		self.orientable		= (orientable == 'o' or orientable == 'O')

		if   num_tetrahedra == 5:
			self.num_manifolds = 415
		elif num_tetrahedra == 6:
			if self.orientable:
				self.num_manifolds = 962
			else:
				self.num_manifolds = 259
		elif num_tetrahedra == 7:
			if self.orientable:
				self.num_manifolds = 3552
			else:
				self.num_manifolds = 887
		else:
			self.num_manifolds = 0
	
	def __repr__(self):
		if   self.num_tetrahedra == 5:
			return 'census of all cusped hyperbolic 3-manifolds triangulable with 5 or fewer ideal tetrahedra'
		elif self.num_tetrahedra == 6:
			if self.orientable:
				return 'census of all orientable cusped hyperbolic 3-manifolds triangulable with 6 (but not fewer) ideal tetrahedra'
			else:
				return 'census of all nonorientable cusped hyperbolic 3-manifolds triangulable with 6 (but not fewer) ideal tetrahedra'
		elif self.num_tetrahedra == 7:
			if self.orientable:
				return 'census of all orientable cusped hyperbolic 3-manifolds triangulable with 7 (but not fewer) ideal tetrahedra'
			else:
				return 'census of all nonorientable cusped hyperbolic 3-manifolds triangulable with 7 (but not fewer) ideal tetrahedra'
		else:
			return 'empty census'
	
	def __len__(self):
		return self.num_manifolds
	
	def __getitem__(self, i):
		if not 0 <= i < self.num_manifolds:
			raise IndexError
		else:
			return Triangulation(self.num_tetrahedra, self.orientable, i)


class AbelianGroup:
	
	def __init__(self, coefficients):
		self.coefficients = coefficients
	
	def __repr__(self):

		coef = self.coefficients
		n = len(coef)

		if (n == 0):
			return 'trivial'
		s = ''
		for i in range(n):
			s = s + 'Z'
			if coef[i] != 0:
				s = s + ('/%i' % coef[i])
			if i != n - 1:
				s = s + ' + '
		return s
	
	def __len__(self):
		return len(self.coefficients)
	
	def __getitem__(self, i):
		return self.coefficients[i]
	
	def Betti_number(self):
		return self.coefficients.count(0)


class FundamentalGroup:

	def __init__(self, fundamental_group):
		#	fundamental_group is an integer that is really
		#	a pointer to a SnapPea kernel FundamentalGroup.
		self.fundamental_group = fundamental_group

	def __del__(self):
		#	Let the SnapPea kernel free its private representation
		#	of a FundamentalGroup.
		SnapPeaC.free_group_presentation(self.fundamental_group)

	def __repr__(self):
		return (	'generators:  ' + self.generators_string() + '\n'
				  + 'relations:\n'
				  + self.relations_string() )
	
	def num_generators(self):
		return SnapPeaC.fg_get_num_generators(self.fundamental_group)
	
	def relations(self):
		return SnapPeaC.fg_get_relations(self.fundamental_group)

	def representation(self, word):
		return SnapPeaC.fg_representation(self.fundamental_group, word)

	def peripheral_curves(self):
		return SnapPeaC.fg_peripheral_curves(self.fundamental_group)
		
	def generators_string(self):
		theAlphabet = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'
		if self.num_generators() > 0:
			return theAlphabet[0 : 2*self.num_generators() - 1]
		else:
			return 'none'
		
	def relations_string(self):
		theString = ''
		for theWord in self.relations():
			theString = theString + theWord + '\n'
		return theString


class SymmetryGroup:

	def __init__(self, symmetry_group, is_full_group, owns_symmetry_group=True):
		#	symmetry_group is an integer that is really
		#	a pointer to a SnapPea kernel SymmetryGroup.
		self.symmetry_group			= symmetry_group
		self.is_full_group			= is_full_group
		self.owns_symmetry_group	= owns_symmetry_group

	def __del__(self):
		#	Let the SnapPea kernel free its private representation
		#	of a SymmetryGroup.
		if self.owns_symmetry_group:
			SnapPeaC.free_symmetry_group(self.symmetry_group)

	def __repr__(self):

		if self.is_full_group:
			thePretext = ''
		else:
			thePretext = 'at least '

		if self.is_abelian():
			theText = self.abelian_description().__repr__()
		elif self.is_dihedral():
			theText = 'D%d'%(self.order()/2)
		elif self.is_polyhedral():
			theText = self.polyhedral_description()['name']
		elif self.is_S5():
			theText = 'S5'
		elif self.is_direct_product():
			theText = 	self.get_factor(0).__repr__()	\
					  + ' X '							\
					  + self.get_factor(1).__repr__()
		else:
			theText = 'nonabelian group of order %d'%self.order()
		
		return thePretext + theText
	
	def order(self):
		return SnapPeaC.symmetry_group_order(self.symmetry_group)
	
	def is_abelian(self):
		return SnapPeaC.symmetry_group_is_abelian(self.symmetry_group)

	def abelian_description(self):
		return AbelianGroup(SnapPeaC.symmetry_group_abelian_description(self.symmetry_group))
	
	def is_dihedral(self):
		return SnapPeaC.symmetry_group_is_dihedral(self.symmetry_group)
	
	def is_polyhedral(self):
		return SnapPeaC.symmetry_group_is_polyhedral(self.symmetry_group)
	
	def polyhedral_description(self):
		return SnapPeaC.symmetry_group_polyhedral_description(self.symmetry_group)
	
	def is_S5(self):
		return SnapPeaC.symmetry_group_is_S5(self.symmetry_group)
	
	def is_direct_product(self):
		return SnapPeaC.symmetry_group_is_direct_product(self.symmetry_group)
	
	def get_factor(self, i):
		return SymmetryGroup(	SnapPeaC.symmetry_group_factor(self.symmetry_group, i),
								True,
								False)
	
	def is_amphicheiral(self):
		return SnapPeaC.symmetry_group_is_amphicheiral(self.symmetry_group)
	
	def is_invertible_knot(self):
		return SnapPeaC.symmetry_group_invertible_knot(self.symmetry_group)
		
	
	#	Please don't confuse abelian_description() (above)
	#	with abelianization() (below).  The former describes
	#	a group which already happens to be abelian, while
	#	the latter gives the quotient of the group by its
	#	commutator subgroup.

	def commutator_subgroup(self):
		return SymmetryGroup(	SnapPeaC.symmetry_group_commutator_subgroup(self.symmetry_group),
								self.is_full_group,
								True)

	def abelianization(self):
	
		if self.is_full_group:
		
			#	Compute the abelianization as a pointer to a SnapPea kernel
			#	internal SymmetryGroup data structure.
			theSnapPeaGroup = SnapPeaC.symmetry_group_abelianization(self.symmetry_group)
			
			#	Create the corresponding Python AbelianGroup object.
			theAbelianization = AbelianGroup(SnapPeaC.symmetry_group_abelian_description(theSnapPeaGroup))

			#	Free the SnapPea kernel's data structure.
			SnapPeaC.free_symmetry_group(theSnapPeaGroup)
			
			#	Return the Python object.
			return theAbelianization
			
		else:
			return None

	def center(self):
		if self.is_full_group:
			return SymmetryGroup(	SnapPeaC.symmetry_group_center(self.symmetry_group),
									True,
									True)
		else:
			return None
	
	def presentation(self):
		if self.is_full_group:
			return SnapPeaC.symmetry_group_presentation(self.symmetry_group)
		else:
			return None
	
	def presentation_text(self):
		if self.is_full_group:
			theAlphabet = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'
			thePresentation = self.presentation()
			theText = '{'
			theText = theText + theAlphabet[0 : 2*thePresentation['number of generators'] - 1]
			theText = theText + ' |'
			theLowerAlphabet = 'abcdefghijklmnopqrstuvwxyz'
			theUpperAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
			theFirstRelationFlag = 1
			for theRelation in thePresentation['relations']:
				if theFirstRelationFlag == 0:
					theText = theText + ',' 
				else:
					theFirstRelationFlag = 0
				for theFactor in theRelation:
					if theFactor[1] < 0:
						theLetter = theUpperAlphabet[theFactor[0]]
						thePower  = - theFactor[1]
					else:
						theLetter = theLowerAlphabet[theFactor[0]]
						thePower =   theFactor[1]
					if thePower > 1:
						theText = theText + ' %c^%i'%(theLetter, thePower)
					elif thePower == 1:
						theText = theText + ' %c'%(theLetter)
					else:
						raise RuntimeError, 'zero exponent in symmetry group presentation'
			theText = theText + ' }'
			return theText
		else:
			return None


class DirichletDomain:

	def __init__(self, Dirichlet_domain):
		#	Dirichlet_domain is an integer that is really
		#	a pointer to a SnapPea kernel WEPolyhedron.
		self.Dirichlet_domain = Dirichlet_domain

	def __del__(self):
		#	Let the SnapPea kernel free its private representation
		#	of a WEPolyhedron.
		SnapPeaC.free_Dirichlet_domain(self.Dirichlet_domain)

	def __repr__(self):
		return 'Dirichlet domain v=? e=? f=?'
	
	def off(self):
	
		theText = 'OFF %d %d 0\n\n'%(self.v(), self.f())
		
		theVertices = self.vertices()
		for i in range(self.v()):
			theText = theText + '%11.8f %11.8f %11.8f\n'%theVertices[i]
		theText = theText + '\n'
		
		theFaces = self.faces()
		theFaceColors = self.face_colors()
		for i in range(self.f()):
			theText = theText + '%2d'%len(theFaces[i])
			for j in range(len(theFaces[i])):
				theText = theText + ' %2d'%theFaces[i][j]
			theText = theText + '  %5.3f %5.3f %5.3f 1.000\n'%theFaceColors[i]
		
		return theText
	
	def v(self):
		return SnapPeaC.Dirichlet_num_vertices(self.Dirichlet_domain)
	
	def e(self):
		return SnapPeaC.Dirichlet_num_edges(self.Dirichlet_domain)
	
	def f(self):
		return SnapPeaC.Dirichlet_num_faces(self.Dirichlet_domain)

	def vertices(self):
		return SnapPeaC.Dirichlet_vertices(self.Dirichlet_domain)

	def faces(self):
		return SnapPeaC.Dirichlet_faces(self.Dirichlet_domain)

	def face_colors(self):
		return SnapPeaC.Dirichlet_face_colors(self.Dirichlet_domain)
	
	def face_pairings(self):
		return SnapPeaC.Dirichlet_face_pairings(self.Dirichlet_domain)