Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/interfaces/chomp.py
8814 views
1
r"""
2
Interface to CHomP
3
4
CHomP stands for "Computation Homology Program", and is good at
5
computing homology of simplicial complexes, cubical complexes, and
6
chain complexes. It can also compute homomorphisms induced on
7
homology by maps. See the CHomP web page http://chomp.rutgers.edu/
8
for more information.
9
10
AUTHOR:
11
12
- John H. Palmieri
13
"""
14
import re
15
16
_have_chomp = {}
17
def have_chomp(program='homsimpl'):
18
"""
19
Return True if this computer has ``program`` installed.
20
21
The first time it is run, this function caches its result in the
22
variable ``_have_chomp`` -- a dictionary indexed by program name
23
-- and any subsequent time, it just checks the value of the
24
variable.
25
26
This program is used in the routine CHomP.__call__.
27
28
If this computer doesn't have CHomP installed, you may obtain it
29
from http://chomp.rutgers.edu/.
30
31
EXAMPLES::
32
33
sage: from sage.interfaces.chomp import have_chomp
34
sage: have_chomp() # random -- depends on whether CHomP is installed
35
True
36
sage: 'homsimpl' in sage.interfaces.chomp._have_chomp
37
True
38
sage: sage.interfaces.chomp._have_chomp['homsimpl'] == have_chomp()
39
True
40
"""
41
global _have_chomp
42
if program not in _have_chomp:
43
from sage.misc.sage_ostools import have_program
44
_have_chomp[program] = have_program(program)
45
return _have_chomp[program]
46
47
class CHomP:
48
"""
49
Interface to the CHomP package.
50
51
:param program: which CHomP program to use
52
:type program: string
53
:param complex: a simplicial or cubical complex
54
:param subcomplex: a subcomplex of ``complex`` or None (the default)
55
:param base_ring: ring over which to perform computations -- must be `\ZZ` or `\GF{p}`.
56
:type base_ring: ring; optional, default `\ZZ`
57
:param generators: if True, also return list of generators
58
:type generators: boolean; optional, default False
59
:param verbose: if True, print helpful messages as the computation
60
progresses
61
:type verbose: boolean; optional, default False
62
:param extra_opts: options passed directly to ``program``
63
:type extra_opts: string
64
:return: homology groups as a dictionary indexed by dimension
65
66
The programs ``homsimpl``, ``homcubes``, and ``homchain`` are
67
available through this interface. ``homsimpl`` computes the
68
relative or absolute homology groups of simplicial complexes.
69
``homcubes`` computes the relative or absolute homology groups of
70
cubical complexes. ``homchain`` computes the homology groups of
71
chain complexes. For consistency with Sage's other homology
72
computations, the answers produced by ``homsimpl`` and
73
``homcubes`` in the absolute case are converted to reduced
74
homology.
75
76
Note also that CHomP can only compute over the integers or
77
`\GF{p}`. CHomP is fast enough, though, that if you want
78
rational information, you should consider using CHomP with integer
79
coefficients, or with mod `p` coefficients for a sufficiently
80
large `p`, rather than using Sage's built-in homology algorithms.
81
82
See also the documentation for the functions :func:`homchain`,
83
:func:`homcubes`, and :func:`homsimpl` for more examples,
84
including illustrations of some of the optional parameters.
85
86
EXAMPLES::
87
88
sage: from sage.interfaces.chomp import CHomP
89
sage: T = cubical_complexes.Torus()
90
sage: CHomP()('homcubes', T) # optional - CHomP
91
{0: 0, 1: Z x Z, 2: Z}
92
93
Relative homology of a segment relative to its endpoints::
94
95
sage: edge = simplicial_complexes.Simplex(1)
96
sage: ends = edge.n_skeleton(0)
97
sage: CHomP()('homsimpl', edge) # optional - CHomP
98
{0: 0}
99
sage: CHomP()('homsimpl', edge, ends) # optional - CHomP
100
{0: 0, 1: Z}
101
102
Homology of a chain complex::
103
104
sage: C = ChainComplex({3: 2 * identity_matrix(ZZ, 2)}, degree=-1)
105
sage: CHomP()('homchain', C) # optional - CHomP
106
{2: C2 x C2}
107
"""
108
def __repr__(self):
109
"""
110
String representation.
111
112
EXAMPLES::
113
114
sage: from sage.interfaces.chomp import CHomP
115
sage: CHomP().__repr__()
116
'CHomP interface'
117
"""
118
return "CHomP interface"
119
120
def __call__(self, program, complex, subcomplex=None, **kwds):
121
"""
122
Call a CHomP program to compute the homology of a chain
123
complex, simplicial complex, or cubical complex.
124
125
See :class:`CHomP` for full documentation.
126
127
EXAMPLES::
128
129
sage: from sage.interfaces.chomp import CHomP
130
sage: T = cubical_complexes.Torus()
131
sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP
132
{0: 0, 1: Z x Z, 2: Z}
133
"""
134
from sage.misc.misc import tmp_filename
135
from sage.homology.all import CubicalComplex, cubical_complexes
136
from sage.homology.all import SimplicialComplex, Simplex
137
from sage.homology.cubical_complex import Cube
138
from sage.homology.chain_complex import HomologyGroup, ChainComplex
139
from subprocess import Popen, PIPE
140
from sage.rings.all import QQ, ZZ
141
from sage.modules.all import VectorSpace, vector
142
from sage.combinat.free_module import CombinatorialFreeModule
143
144
if not have_chomp(program):
145
raise OSError, "Program %s not found" % program
146
147
verbose = kwds.get('verbose', False)
148
generators = kwds.get('generators', False)
149
extra_opts = kwds.get('extra_opts', '')
150
base_ring = kwds.get('base_ring', ZZ)
151
152
if extra_opts:
153
extra_opts = extra_opts.split()
154
else:
155
extra_opts = []
156
157
# type of complex:
158
cubical = False
159
simplicial = False
160
chain = False
161
# CHomP seems to have problems with cubical complexes if the
162
# first interval in the first cube defining the complex is
163
# degenerate. So replace the complex X with [0,1] x X.
164
if isinstance(complex, CubicalComplex):
165
cubical = True
166
edge = cubical_complexes.Cube(1)
167
original_complex = complex
168
complex = edge.product(complex)
169
if verbose:
170
print "Cubical complex"
171
elif isinstance(complex, SimplicialComplex):
172
simplicial = True
173
if verbose:
174
print "Simplicial complex"
175
else:
176
chain = True
177
base_ring = kwds.get('base_ring', complex.base_ring())
178
if verbose:
179
print "Chain complex over %s" % base_ring
180
181
if base_ring == QQ:
182
raise ValueError, "CHomP doesn't compute over the rationals, only over Z or F_p."
183
if base_ring.is_prime_field():
184
p = base_ring.characteristic()
185
extra_opts.append('-p%s' % p)
186
mod_p = True
187
else:
188
mod_p = False
189
190
#
191
# complex
192
#
193
try:
194
data = complex._chomp_repr_()
195
except AttributeError:
196
raise AttributeError, "Complex can not be converted to use with CHomP."
197
198
datafile = tmp_filename()
199
f = open(datafile, 'w')
200
f.write(data)
201
f.close()
202
203
#
204
# subcomplex
205
#
206
if subcomplex is None:
207
if cubical:
208
subcomplex = CubicalComplex([complex.n_cells(0)[0]])
209
elif simplicial:
210
m = re.search(r'\(([^,]*),', data)
211
v = int(m.group(1))
212
subcomplex = SimplicialComplex([[v]])
213
else:
214
# replace subcomplex with [0,1] x subcomplex.
215
if cubical:
216
subcomplex = edge.product(subcomplex)
217
#
218
# generators
219
#
220
if generators:
221
genfile = tmp_filename()
222
extra_opts.append('-g%s' % genfile)
223
224
#
225
# call program
226
#
227
if subcomplex is not None:
228
try:
229
sub = subcomplex._chomp_repr_()
230
except AttributeError:
231
raise AttributeError, "Subcomplex can not be converted to use with CHomP."
232
subfile = tmp_filename()
233
f = open(subfile, 'w')
234
f.write(sub)
235
f.close()
236
else:
237
subfile = ''
238
if verbose:
239
print "Popen called with arguments",
240
print [program, datafile, subfile] + extra_opts
241
print
242
print "CHomP output:"
243
print
244
# output = Popen([program, datafile, subfile, extra_opts],
245
cmd = [program, datafile]
246
if subfile:
247
cmd.append(subfile)
248
if extra_opts:
249
cmd.extend(extra_opts)
250
output = Popen(cmd, stdout=PIPE).communicate()[0]
251
if verbose:
252
print output
253
print "End of CHomP output"
254
print
255
if generators:
256
gens = open(genfile, 'r').read()
257
if verbose:
258
print "Generators:"
259
print gens
260
261
#
262
# process output
263
#
264
# output contains substrings of one of the forms
265
# "H_1 = Z", "H_1 = Z_2 + Z", "H_1 = Z_2 + Z^2",
266
# "H_1 = Z + Z_2 + Z"
267
if output.find('trivial') != -1:
268
if mod_p:
269
return {0: VectorSpace(base_ring, 0)}
270
else:
271
return {0: HomologyGroup(0, ZZ)}
272
d = {}
273
h = re.compile("^H_([0-9]*) = (.*)$", re.M)
274
tors = re.compile("Z_([0-9]*)")
275
#
276
# homology groups
277
#
278
for m in h.finditer(output):
279
if verbose:
280
print m.groups()
281
# dim is the dimension of the homology group
282
dim = int(m.group(1))
283
# hom_str is the right side of the equation "H_n = Z^r + Z_k + ..."
284
hom_str = m.group(2)
285
# need to read off number of summands and their invariants
286
if hom_str.find("0") == 0:
287
if mod_p:
288
hom = VectorSpace(base_ring, 0)
289
else:
290
hom = HomologyGroup(0, ZZ)
291
else:
292
rk = 0
293
if hom_str.find("^") != -1:
294
rk_srch = re.search(r'\^([0-9]*)\s?', hom_str)
295
rk = int(rk_srch.group(1))
296
rk += len(re.findall("(Z$)|(Z\s)", hom_str))
297
if mod_p:
298
rk = rk if rk != 0 else 1
299
if verbose:
300
print "dimension = %s, rank of homology = %s" % (dim, rk)
301
hom = VectorSpace(base_ring, rk)
302
else:
303
n = rk
304
invts = []
305
for t in tors.finditer(hom_str):
306
n += 1
307
invts.append(int(t.group(1)))
308
for i in range(rk):
309
invts.append(0)
310
if verbose:
311
print "dimension = %s, number of factors = %s, invariants = %s" %(dim, n, invts)
312
hom = HomologyGroup(n, ZZ, invts)
313
314
#
315
# generators
316
#
317
if generators:
318
if cubical:
319
g = process_generators_cubical(gens, dim)
320
if verbose:
321
print "raw generators: %s" % g
322
if g:
323
module = CombinatorialFreeModule(base_ring,
324
original_complex.n_cells(dim),
325
prefix="",
326
bracket=True)
327
basis = module.basis()
328
output = []
329
for x in g:
330
v = module(0)
331
for term in x:
332
v += term[0] * basis[term[1]]
333
output.append(v)
334
g = output
335
elif simplicial:
336
g = process_generators_simplicial(gens, dim, complex)
337
if verbose:
338
print "raw generators: %s" % gens
339
if g:
340
module = CombinatorialFreeModule(base_ring,
341
complex.n_cells(dim),
342
prefix="",
343
bracket=False)
344
basis = module.basis()
345
output = []
346
for x in g:
347
v = module(0)
348
for term in x:
349
if complex._is_numeric():
350
v += term[0] * basis[term[1]]
351
else:
352
translate = complex._translation_from_numeric()
353
simplex = Simplex([translate[a] for a in term[1]])
354
v += term[0] * basis[simplex]
355
output.append(v)
356
g = output
357
elif chain:
358
g = process_generators_chain(gens, dim, base_ring)
359
if verbose:
360
print "raw generators: %s" % gens
361
if g:
362
if not mod_p:
363
# sort generators to match up with corresponding invariant
364
g = [_[1] for _ in sorted(zip(invts, g), cmp=lambda x,y: cmp(x[0], y[0]))]
365
d[dim] = (hom, g)
366
else:
367
d[dim] = hom
368
else:
369
d[dim] = hom
370
371
if chain:
372
new_d = {}
373
diff = complex.differential()
374
if len(diff) == 0:
375
return {}
376
bottom = min(diff)
377
top = max(diff)
378
for dim in d:
379
if complex._degree_of_differential == -1: # chain complex
380
new_dim = bottom + dim
381
else: # cochain complex
382
new_dim = top - dim
383
if isinstance(d[dim], tuple):
384
# generators included.
385
group = d[dim][0]
386
gens = d[dim][1]
387
new_gens = []
388
dimension = complex.differential(new_dim).ncols()
389
# make sure that each vector is embedded in the
390
# correct ambient space: pad with a zero if
391
# necessary.
392
for v in gens:
393
v_dict = v.dict()
394
if dimension - 1 not in v.dict():
395
v_dict[dimension - 1] = 0
396
new_gens.append(vector(base_ring, v_dict))
397
else:
398
new_gens.append(v)
399
new_d[new_dim] = (group, new_gens)
400
else:
401
new_d[new_dim] = d[dim]
402
d = new_d
403
return d
404
405
def help(self, program):
406
"""
407
Print a help message for ``program``, a program from the CHomP suite.
408
409
:param program: which CHomP program to use
410
:type program: string
411
:return: nothing -- just print a message
412
413
EXAMPLES::
414
415
sage: from sage.interfaces.chomp import CHomP
416
sage: CHomP().help('homcubes') # optional - CHomP
417
HOMCUBES, ver. ... Copyright (C) ... by Pawel Pilarczyk...
418
"""
419
from subprocess import Popen, PIPE
420
print Popen([program, '-h'], stdout=PIPE).communicate()[0]
421
422
def homsimpl(complex=None, subcomplex=None, **kwds):
423
r"""
424
Compute the homology of a simplicial complex using the CHomP
425
program ``homsimpl``. If the argument ``subcomplex`` is present,
426
compute homology of ``complex`` relative to ``subcomplex``.
427
428
:param complex: a simplicial complex
429
:param subcomplex: a subcomplex of ``complex`` or None (the default)
430
:param base_ring: ring over which to perform computations -- must be `\ZZ` or `\GF{p}`.
431
:type base_ring: ring; optional, default `\ZZ`
432
:param generators: if True, also return list of generators
433
:type generators: boolean; optional, default False
434
:param verbose: if True, print helpful messages as the computation
435
progresses
436
:type verbose: boolean; optional, default False
437
:param help: if True, just print a help message and exit
438
:type help: boolean; optional, default False
439
:param extra_opts: options passed directly to ``program``
440
:type extra_opts: string
441
:return: homology groups as a dictionary indexed by dimension
442
443
EXAMPLES::
444
445
sage: from sage.interfaces.chomp import homsimpl
446
sage: T = simplicial_complexes.Torus()
447
sage: M8 = simplicial_complexes.MooreSpace(8)
448
sage: M4 = simplicial_complexes.MooreSpace(4)
449
sage: X = T.disjoint_union(T).disjoint_union(T).disjoint_union(M8).disjoint_union(M4)
450
sage: homsimpl(X)[1] # optional - CHomP
451
Z^6 x C4 x C8
452
453
Relative homology::
454
455
sage: S = simplicial_complexes.Simplex(3)
456
sage: bdry = S.n_skeleton(2)
457
sage: homsimpl(S, bdry)[3] # optional - CHomP
458
Z
459
460
Generators: these are given as a list after the homology group.
461
Each generator is specified as a linear combination of simplices::
462
463
sage: homsimpl(S, bdry, generators=True)[3] # optional - CHomP
464
(Z, [(0, 1, 2, 3)])
465
466
sage: homsimpl(simplicial_complexes.Sphere(1), generators=True) # optional - CHomP
467
{0: 0, 1: (Z, [(0, 1) - (0, 2) + (1, 2)])}
468
469
TESTS:
470
471
Generators for a simplicial complex whose vertices are not integers::
472
473
sage: S1 = simplicial_complexes.Sphere(1)
474
sage: homsimpl(S1.join(S1), generators=True, base_ring=GF(2))[3][1] # optional - CHomP
475
[('L0', 'L1', 'R0', 'R1') + ('L0', 'L1', 'R0', 'R2') + ('L0', 'L1', 'R1', 'R2') + ('L0', 'L2', 'R0', 'R1') + ('L0', 'L2', 'R0', 'R2') + ('L0', 'L2', 'R1', 'R2') + ('L1', 'L2', 'R0', 'R1') + ('L1', 'L2', 'R0', 'R2') + ('L1', 'L2', 'R1', 'R2')]
476
"""
477
from sage.homology.all import SimplicialComplex
478
help = kwds.get('help', False)
479
if help:
480
return CHomP().help('homsimpl')
481
# Check types here, because if you pass homsimpl a cubical
482
# complex, it tries to compute its homology as if it were a
483
# simplicial complex and gets terribly wrong answers.
484
if (isinstance(complex, SimplicialComplex)
485
and (subcomplex is None or isinstance(subcomplex, SimplicialComplex))):
486
return CHomP()('homsimpl', complex, subcomplex=subcomplex, **kwds)
487
else:
488
raise TypeError, "Complex and/or subcomplex are not simplicial complexes."
489
490
def homcubes(complex=None, subcomplex=None, **kwds):
491
r"""
492
Compute the homology of a cubical complex using the CHomP program
493
``homcubes``. If the argument ``subcomplex`` is present, compute
494
homology of ``complex`` relative to ``subcomplex``.
495
496
:param complex: a cubical complex
497
:param subcomplex: a subcomplex of ``complex`` or None (the default)
498
:param generators: if True, also return list of generators
499
:type generators: boolean; optional, default False
500
:param verbose: if True, print helpful messages as the computation progresses
501
:type verbose: boolean; optional, default False
502
:param help: if True, just print a help message and exit
503
:type help: boolean; optional, default False
504
:param extra_opts: options passed directly to ``homcubes``
505
:type extra_opts: string
506
:return: homology groups as a dictionary indexed by dimension
507
508
EXAMPLES::
509
510
sage: from sage.interfaces.chomp import homcubes
511
sage: S = cubical_complexes.Sphere(3)
512
sage: homcubes(S)[3] # optional - CHomP
513
Z
514
515
Relative homology::
516
517
sage: C3 = cubical_complexes.Cube(3)
518
sage: bdry = C3.n_skeleton(2)
519
sage: homcubes(C3, bdry) # optional - CHomP
520
{0: 0, 1: 0, 2: 0, 3: Z}
521
522
Generators: these are given as a list after the homology group.
523
Each generator is specified as a linear combination of cubes::
524
525
sage: homcubes(cubical_complexes.Sphere(1), generators=True, base_ring=GF(2))[1][1] # optional - CHomP
526
[[[1,1] x [0,1]] + [[0,1] x [1,1]] + [[0,1] x [0,0]] + [[0,0] x [0,1]]]
527
"""
528
from sage.homology.all import CubicalComplex
529
help = kwds.get('help', False)
530
if help:
531
return CHomP().help('homcubes')
532
# Type-checking is here for the same reason as in homsimpl above.
533
if (isinstance(complex, CubicalComplex)
534
and (subcomplex is None or isinstance(subcomplex, CubicalComplex))):
535
return CHomP()('homcubes', complex, subcomplex=subcomplex, **kwds)
536
else:
537
raise TypeError, "Complex and/or subcomplex are not cubical complexes."
538
539
def homchain(complex=None, **kwds):
540
r"""
541
Compute the homology of a chain complex using the CHomP program
542
``homchain``.
543
544
:param complex: a chain complex
545
:param generators: if True, also return list of generators
546
:type generators: boolean; optional, default False
547
:param verbose: if True, print helpful messages as the computation progresses
548
:type verbose: boolean; optional, default False
549
:param help: if True, just print a help message and exit
550
:type help: boolean; optional, default False
551
:param extra_opts: options passed directly to ``homchain``
552
:type extra_opts: string
553
:return: homology groups as a dictionary indexed by dimension
554
555
EXAMPLES::
556
557
sage: from sage.interfaces.chomp import homchain
558
sage: C = cubical_complexes.Sphere(3).chain_complex()
559
sage: homchain(C)[3] # optional - CHomP
560
Z
561
562
Generators: these are given as a list after the homology group.
563
Each generator is specified as a cycle, an element in the
564
appropriate free module over the base ring::
565
566
sage: C2 = delta_complexes.Sphere(2).chain_complex()
567
sage: homchain(C2, generators=True)[2] # optional - CHomP
568
(Z, [(1, -1)])
569
sage: homchain(C2, generators=True, base_ring=GF(2))[2] # optional - CHomP
570
(Vector space of dimension 1 over Finite Field of size 2, [(1, 1)])
571
572
TESTS:
573
574
Chain complexes concentrated in negative dimensions, cochain complexes, etc.::
575
576
sage: C = ChainComplex({-5: 4 * identity_matrix(ZZ, 2)}, degree=-1)
577
sage: homchain(C) # optional - CHomP
578
{-6: C4 x C4}
579
sage: C = ChainComplex({-5: 4 * identity_matrix(ZZ, 2)}, degree=1)
580
sage: homchain(C, generators=True) # optional - CHomP
581
{-4: (C4 x C4, [(1, 0), (0, 1)])}
582
"""
583
from sage.homology.chain_complex import ChainComplex_class
584
help = kwds.get('help', False)
585
if help:
586
return CHomP().help('homchain')
587
# Type-checking just in case.
588
if isinstance(complex, ChainComplex_class):
589
return CHomP()('homchain', complex, **kwds)
590
else:
591
raise TypeError, "Complex is not a chain complex."
592
593
def process_generators_cubical(gen_string, dim):
594
r"""
595
Process CHomP generator information for cubical complexes.
596
597
:param gen_string: generator output from CHomP
598
:type gen_string: string
599
:param dim: dimension in which to find generators
600
:type dim: integer
601
:return: list of generators in each dimension, as described below
602
603
``gen_string`` has the form ::
604
605
The 2 generators of H_1 follow:
606
generator 1
607
-1 * [(0,0,0,0,0)(0,0,0,0,1)]
608
1 * [(0,0,0,0,0)(0,0,1,0,0)]
609
...
610
generator 2
611
-1 * [(0,1,0,1,1)(1,1,0,1,1)]
612
-1 * [(0,1,0,0,1)(0,1,0,1,1)]
613
...
614
615
Each line consists of a coefficient multiplied by a cube; the cube
616
is specified by its "bottom left" and "upper right" corners.
617
618
For technical reasons, we remove the first coordinate of each
619
tuple, and using regular expressions, the remaining parts get
620
converted from a string to a pair ``(coefficient, Cube)``, with
621
the cube represented as a product of tuples. For example, the
622
first line in "generator 1" gets turned into ::
623
624
(-1, [0,0] x [0,0] x [0,0] x [0,1])
625
626
representing an element in the free abelian group with basis given
627
by cubes. Each generator is a list of such pairs, representing
628
the sum of such elements. These are reassembled in
629
:meth:`CHomP.__call__` to actual elements in the free module
630
generated by the cubes of the cubical complex in the appropriate
631
dimension.
632
633
Therefore the return value is a list of lists of pairs, one list
634
of pairs for each generator.
635
636
EXAMPLES::
637
638
sage: from sage.interfaces.chomp import process_generators_cubical
639
sage: s = "The 2 generators of H_1 follow:\ngenerator 1:\n-1 * [(0,0,0,0,0)(0,0,0,0,1)]\n1 * [(0,0,0,0,0)(0,0,1,0,0)]"
640
sage: process_generators_cubical(s, 1)
641
[[(-1, [0,0] x [0,0] x [0,0] x [0,1]), (1, [0,0] x [0,1] x [0,0] x [0,0])]]
642
sage: len(process_generators_cubical(s, 1)) # only one generator
643
1
644
"""
645
from sage.homology.cubical_complex import Cube
646
# each dim in gen_string starts with "The generator for
647
# H_3 follows:". So search for "T" to find the
648
# end of the current list of generators.
649
g_srch = re.compile(r'H_%s\sfollow[s]?:\n([^T]*)(?:T|$)' % dim)
650
g = g_srch.search(gen_string)
651
output = []
652
cubes_list = []
653
if g:
654
g = g.group(1)
655
if g:
656
# project g to one end of the cylinder [0,1] x complex:
657
#
658
# drop the first coordinate and eliminate duplicates, at least
659
# in positive dimensions, drop any line containing a
660
# degenerate cube
661
g = re.sub('\([01],', '(', g)
662
if dim > 0:
663
lines = g.splitlines()
664
newlines = []
665
for l in lines:
666
x = re.findall(r'(\([0-9,]*\))', l)
667
if x:
668
left, right = x
669
left = [int(a) for a in left.strip('()').split(',')]
670
right = [int(a) for a in right.strip('()').split(',')]
671
if sum([x-y for (x,y) in zip(right, left)]) == dim:
672
newlines.append(l)
673
else: # line like "generator 2"
674
newlines.append(l)
675
g = newlines
676
cubes_list = []
677
for l in g:
678
cubes = re.search(r'([+-]?)\s?([0-9]+)?\s?[*]?\s?\[\(([-0-9,]+)\)\(([-0-9,]+)\)\]', l)
679
if cubes:
680
if cubes.group(1) and re.search("-", cubes.group(1)):
681
sign = -1
682
else:
683
sign = 1
684
if cubes.group(2) and len(cubes.group(2)) > 0:
685
coeff = sign * int(cubes.group(2))
686
else:
687
coeff = sign * 1
688
cube1 = [int(a) for a in cubes.group(3).split(',')]
689
cube2 = [int(a) for a in cubes.group(4).split(',')]
690
cube = Cube(zip(cube1, cube2))
691
cubes_list.append((coeff, cube))
692
else:
693
if cubes_list:
694
output.append(cubes_list)
695
cubes_list = []
696
if cubes_list:
697
output.append(cubes_list)
698
return output
699
else:
700
return None
701
702
def process_generators_simplicial(gen_string, dim, complex):
703
r"""
704
Process CHomP generator information for simplicial complexes.
705
706
:param gen_string: generator output from CHomP
707
:type gen_string: string
708
:param dim: dimension in which to find generators
709
:type dim: integer
710
:param complex: simplicial complex under consideration
711
:return: list of generators in each dimension, as described below
712
713
``gen_string`` has the form ::
714
715
The 2 generators of H_1 follow:
716
generator 1
717
-1 * (1,6)
718
1 * (1,4)
719
...
720
generator 2
721
-1 * (1,6)
722
1 * (1,4)
723
...
724
725
where each line contains a coefficient and a simplex. Each line
726
is converted, using regular expressions, from a string to a pair
727
``(coefficient, Simplex)``, like ::
728
729
(-1, (1,6))
730
731
representing an element in the free abelian group with basis given
732
by simplices. Each generator is a list of such pairs,
733
representing the sum of such elements. These are reassembled in
734
:meth:`CHomP.__call__` to actual elements in the free module
735
generated by the simplices of the simplicial complex in the
736
appropriate dimension.
737
738
Therefore the return value is a list of lists of pairs, one list
739
of pairs for each generator.
740
741
EXAMPLES::
742
743
sage: from sage.interfaces.chomp import process_generators_simplicial
744
sage: s = "The 2 generators of H_1 follow:\ngenerator 1:\n-1 * (1,6)\n1 * (1,4)"
745
sage: process_generators_simplicial(s, 1, simplicial_complexes.Torus())
746
[[(-1, (1, 6)), (1, (1, 4))]]
747
"""
748
from sage.homology.all import Simplex
749
# each dim in gen_string starts with "The generator for H_3
750
# follows:". So search for "T" to find the end of the current
751
# list of generators.
752
g_srch = re.compile(r'H_%s\sfollow[s]?:\n([^T]*)(?:T|$)' % dim)
753
g = g_srch.search(gen_string)
754
output = []
755
simplex_list = []
756
if g:
757
g = g.group(1)
758
if g:
759
lines = g.splitlines()
760
newlines = []
761
for l in lines:
762
simplex = re.search(r'([+-]?)\s?([0-9]+)?\s?[*]?\s?(\([0-9,]*\))', l)
763
if simplex:
764
if simplex.group(1) and re.search("-", simplex.group(1)):
765
sign = -1
766
else:
767
sign = 1
768
if simplex.group(2) and len(simplex.group(2)) > 0:
769
coeff = sign * int(simplex.group(2))
770
else:
771
coeff = sign * 1
772
simplex = Simplex([int(a) for a in simplex.group(3).strip('()').split(',')])
773
simplex_list.append((coeff, simplex))
774
else:
775
if simplex_list:
776
output.append(simplex_list)
777
simplex_list = []
778
if simplex_list:
779
output.append(simplex_list)
780
return output
781
else:
782
return None
783
784
def process_generators_chain(gen_string, dim, base_ring=None):
785
r"""
786
Process CHomP generator information for simplicial complexes.
787
788
:param gen_string: generator output from CHomP
789
:type gen_string: string
790
:param dim: dimension in which to find generators
791
:type dim: integer
792
:param base_ring: base ring over which to do the computations
793
:type base_ring: optional, default ZZ
794
:return: list of generators in each dimension, as described below
795
796
``gen_string`` has the form ::
797
798
[H_0]
799
a1
800
801
[H_1]
802
a2
803
a3
804
805
[H_2]
806
a1 - a2
807
808
For each homology group, each line lists a homology generator as a
809
linear combination of generators ``ai`` of the group of chains in
810
the appropriate dimension. The elements ``ai`` are indexed
811
starting with `i=1`. Each generator is converted, using regular
812
expressions, from a string to a vector (an element in the free
813
module over ``base_ring``), with ``ai`` representing the unit
814
vector in coordinate `i-1`. For example, the string ``a1 - a2``
815
gets converted to the vector ``(1, -1)``.
816
817
Therefore the return value is a list of vectors.
818
819
EXAMPLES::
820
821
sage: from sage.interfaces.chomp import process_generators_chain
822
sage: s = "[H_0]\na1\n\n[H_1]\na2\na3\n"
823
sage: process_generators_chain(s, 1)
824
[(0, 1), (0, 0, 1)]
825
sage: s = "[H_0]\na1\n\n[H_1]\n5 * a2 - a1\na3\n"
826
sage: process_generators_chain(s, 1, base_ring=ZZ)
827
[(-1, 5), (0, 0, 1)]
828
sage: process_generators_chain(s, 1, base_ring=GF(2))
829
[(1, 1), (0, 0, 1)]
830
"""
831
from sage.modules.all import vector
832
from sage.rings.all import ZZ
833
if base_ring is None:
834
base_ring = ZZ
835
# each dim in gens starts with a string like
836
# "[H_3]". So search for "[" to find the end of
837
# the current list of generators.
838
g_srch = re.compile(r'\[H_%s\]\n([^]]*)(?:\[|$)' % dim)
839
g = g_srch.search(gen_string)
840
if g:
841
g = g.group(1)
842
if g:
843
# each line in the string g is a linear
844
# combination of things like "a2", "a31", etc.
845
# indexing on the a's starts at 1.
846
lines = g.splitlines()
847
new_gens = []
848
for l in lines:
849
gen = re.compile(r"([+-]?)\s?([0-9]+)?\s?[*]?\s?a([0-9]*)(?:\s|$)")
850
v = {}
851
for term in gen.finditer(l):
852
if term.group(1) and re.search("-", term.group(1)):
853
sign = -1
854
else:
855
sign = 1
856
if term.group(2) and len(term.group(2)) > 0:
857
coeff = sign * int(term.group(2))
858
else:
859
coeff = sign * 1
860
idx = int(term.group(3))
861
v[idx-1] = coeff
862
if v:
863
new_gens.append(vector(base_ring, v))
864
g = new_gens
865
return g
866
867