Skip to content

Commit

Permalink
Merge branch 'master' into pythonprinter2
Browse files Browse the repository at this point in the history
  • Loading branch information
bjodah committed Aug 9, 2017
2 parents 998946c + accb35d commit d4a6198
Show file tree
Hide file tree
Showing 69 changed files with 2,379 additions and 478 deletions.
553 changes: 328 additions & 225 deletions .ci/durations.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .ci/generate_durations_log.sh
@@ -1,3 +1,3 @@
#!/bin/bash
ABS_REPO_PATH=$(unset CDPATH && cd "$(dirname "$0")/.." && echo $PWD)
python3 -m pytest --durations 0 --slow --veryslow >$ABS_REPO_PATH/.ci/durations.log
python3 -m pytest --durations 0 >$ABS_REPO_PATH/.ci/durations.log
2 changes: 1 addition & 1 deletion .ci/parse_durations_log.py
Expand Up @@ -24,7 +24,7 @@ def read_log():
continue
if dur[-1] != 's':
raise NotImplementedError("expected seconds")
yield test_id, float(time[:-1])
yield test_id, float(dur[:-1])
elif start_token in line:
start_token_seen = True

Expand Down
9 changes: 7 additions & 2 deletions .travis.yml
Expand Up @@ -290,18 +290,23 @@ before_install:
sudo apt-get install -y sagemath-upstream-binary;
fi
install:
# If a command fails, fail the build.
- set -e
#The install cycle below is to test installation on systems without setuptools.
- if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ];
then virtualenv -p /usr/bin/pypy ~/.venv;
. ~/.venv/bin/activate;
fi
# -We:invalid makes invalid escape sequences error in Python 3.6. See
# -#12028.
- if [[ "${TEST_SAGE}" != "true" ]]; then
pip install mpmath;
pip uninstall -y setuptools;
python setup.py install;
python -We:invalid setup.py install;
pip uninstall -y sympy;
pip install --upgrade setuptools;
python setup.py install;
python -We:invalid -m compileall -f sympy/;
python -We:invalid setup.py install;
fi
script:
# Don't run doctr if the build fails
Expand Down
3 changes: 2 additions & 1 deletion sympy/__init__.py
Expand Up @@ -11,6 +11,7 @@
"""


from __future__ import absolute_import, print_function
del absolute_import, print_function

Expand Down Expand Up @@ -80,7 +81,7 @@ def __sympy_debug():
from .printing import pretty, pretty_print, pprint, pprint_use_unicode, \
pprint_try_use_unicode, print_gtk, print_tree, pager_print, TableForm
from .printing import rcode, ccode, fcode, jscode, julia_code, mathematica_code, \
octave_code, latex, preview, rust_code, mathml, cxxcode
octave_code, latex, preview, rust_code, mathml, glsl_code, cxxcode
from .printing import python, print_python, srepr, sstr, sstrrepr
from .interactive import init_session, init_printing

Expand Down
2 changes: 1 addition & 1 deletion sympy/combinatorics/coset_table.py
Expand Up @@ -680,7 +680,7 @@ def switch(self, beta, gamma):
table[alpha][A_dict[x]] = beta

def standardize(self):
"""
r"""
A coset table is standardized if when running through the cosets and
within each coset through the generator images (ignoring generator
inverses), the cosets appear in order of the integers
Expand Down
4 changes: 2 additions & 2 deletions sympy/combinatorics/fp_groups.py
Expand Up @@ -248,7 +248,7 @@ def _finite_index_subgroup(self, s=[]):
i = 0
while ((rand in rels or rand**-1 in rels or rand.is_identity)
and i<10):
rand = self.random_element()
rand = self.random()
i += 1
s = [gen, rand] + [g for g in self.generators if g != gen]
mid = (len(s)+1)//2
Expand Down Expand Up @@ -281,7 +281,7 @@ def most_frequent_generator(self):
freqs = [sum([r.generator_count(g) for r in rels]) for g in gens]
return gens[freqs.index(max(freqs))]

def random_element(self):
def random(self):
import random
r = self.free_group.identity
for i in range(random.randint(2,3)):
Expand Down
37 changes: 9 additions & 28 deletions sympy/combinatorics/free_groups.py
Expand Up @@ -262,25 +262,6 @@ def rank(self):
"""
return self._rank

def _symbol_index(self, symbol):
"""Returns the index of a generator for free group `self`, while
returns the -ve index of the inverse generator.
Examples
========
>>> from sympy.combinatorics.free_groups import free_group
>>> from sympy import Symbol
>>> F, x, y = free_group("x, y")
>>> F._symbol_index(-Symbol('x'))
0
"""
try:
return self.symbols.index(symbol)
except ValueError:
return -self.symbols.index(-symbol)

@property
def is_abelian(self):
"""Returns if the group is Abelian.
Expand Down Expand Up @@ -682,7 +663,7 @@ def eliminate_word(self, gen, by=None, _all=False, inverse=True):
word = word.subword(0, i)*by**k*word.subword(i+l, len(word)).eliminate_word(gen, by)

if _all:
return word.eliminate_word(gen, by, _all=True)
return word.eliminate_word(gen, by, _all=True, inverse=inverse)
else:
return word

Expand Down Expand Up @@ -774,18 +755,18 @@ def __lt__(self, other):
return True
elif l > m:
return False
a = self.letter_form
b = other.letter_form
for i in range(l):
p = group._symbol_index(a[i])
q = group._symbol_index(b[i])
if abs(p) < abs(q):
a = self[i].array_form[0]
b = other[i].array_form[0]
p = group.symbols.index(a[0])
q = group.symbols.index(b[0])
if p < q:
return True
elif abs(p) > abs(q):
elif p > q:
return False
elif p < q:
elif a[1] < b[1]:
return True
elif p > q:
elif a[1] > b[1]:
return False
return False

Expand Down
147 changes: 112 additions & 35 deletions sympy/combinatorics/homomorphisms.py
Expand Up @@ -36,35 +36,61 @@ def _invs(self):
if not (v in inverses
or v.is_identity):
inverses[v] = k
gens = image.strong_gens
if isinstance(self.codomain, PermutationGroup):
gens = image.strong_gens
else:
gens = image.generators
for g in gens:
if g in inverses or g.is_identity:
continue
w = self.domain.identity
for s in image._strong_gens_slp[g]:
if isinstance(self.codomain, PermutationGroup):
parts = image._strong_gens_slp[g][::-1]
else:
parts = g
for s in parts:
if s in inverses:
w = inverses[s]*w
w = w*inverses[s]
else:
w = inverses[s**-1]**-1*w
w = w*inverses[s**-1]**-1
inverses[g] = w

return inverses

def invert(self, g):
'''
Return an element of the preimage of `g`
Return an element of the preimage of `g`.
NOTE: If the codomain is an FpGroup, the inverse for equal
elements might not always be the same unless the FpGroup's
rewriting system is confluent. However, making a system
confluent can be time-consuming. If it's important, try
`self.codomain.make_confluent()` first.
'''
if not isinstance(self.codomain, PermutationGroup):
raise NotImplementedError(
"Only elements of PermutationGroups can be inverted")
if isinstance(self.codomain, FpGroup):
g = self.codomain.reduce(g)
if self._inverses is None:
self._inverses = self._invs()
image = self.image()
w = self.domain.identity
for g in image.generator_product(g):
if g.is_identity:
if isinstance(self.codomain, PermutationGroup):
gens = image.generator_product(g)[::-1]
else:
gens = g
# the following can't be "for s in gens:"
# because that would be equivalent to
# "for s in gens.array_form:" when g is
# a FreeGroupElement. On the other hand,
# when you call gens by index, the generator
# (or inverse) at position i is returned.
for i in range(len(gens)):
s = gens[i]
if s.is_identity:
continue
w = self._inverses[g]*w
if s in self._inverses:
w = w*self._inverses[s]
else:
w = w*self._inverses[s**-1]**-1
return w

def kernel(self):
Expand All @@ -84,14 +110,20 @@ def _compute_kernel(self):
raise NotImplementedError(
"Kernel computation is not implemented for infinite groups")
gens = []
K = FpSubgroup(G, gens, normal=True)
if isinstance(G, PermutationGroup):
K = PermutationGroup(G.identity)
else:
K = FpSubgroup(G, gens, normal=True)
i = self.image().order()
while K.order()*i != G_order:
r = G.random_element()
k = r*self.invert(self(r))
r = G.random()
k = r*self.invert(self(r))**-1
if not k in K:
gens.append(k)
K = FpSubgroup(G, gens, normal=True)
if isinstance(G, PermutationGroup):
K = PermutationGroup(gens)
else:
K = FpSubgroup(G, gens, normal=True)
return K

def image(self):
Expand All @@ -103,28 +135,39 @@ def image(self):
values = list(set(self.images.values()))
if isinstance(self.codomain, PermutationGroup):
self._image = self.codomain.subgroup(values)
elif isinstance(self.codomain, FpGroup):
self._image = FpSubgroup(self.codomain, values)
else:
self._image = FreeSubgroup(self.codomain, values)
self._image = FpSubgroup(self.codomain, values)
return self._image

def _apply(self, elem):
'''
Apply `self` to `elem`.
'''
if not elem in self.domain.free_group:
if not elem in self.domain:
raise ValueError("The supplied element doesn't belong to the domain")
if elem.is_identity:
return self.codomain.identity
else:
p = elem.array_form[0][1]
if p < 0:
g = elem[0]**-1
images = self.images
value = self.codomain.identity
if isinstance(self.domain, PermutationGroup):
gens = self.domain.generator_product(elem, original=True)
for g in gens:
if g in self.images:
value = images[g]*value
else:
value = images[g**-1]**-1*value
else:
g = elem[0]
return self.images[g]**p*self._apply(elem.subword(abs(p), len(elem)))
i = 0
for _, p in elem.array_form:
if p < 0:
g = elem[i]**-1
else:
g = elem[i]
value = value*images[g]**p
i += abs(p)
return value

def __call__(self, elem):
return self._apply(elem)
Expand Down Expand Up @@ -164,7 +207,7 @@ def is_trivial(self):
'''
return self.image().order() == 1

def homomorphism(domain, codomain, gens, images=[]):
def homomorphism(domain, codomain, gens, images=[], check=True):
'''
Create (if possible) a group homomorphism from the group `domain`
to the group `codomain` defined by the images of the domain's
Expand All @@ -176,10 +219,16 @@ def homomorphism(domain, codomain, gens, images=[]):
If the given images of the generators do not define a homomorphism,
an exception is raised.
If `check` is `False`, don't check whether the given images actually
define a homomorphism.
'''
if isinstance(domain, PermutationGroup):
raise NotImplementedError("Homomorphisms from permutation groups are not currently implemented")
elif not isinstance(domain, (FpGroup, FreeGroup)):
if check and isinstance(domain, PermutationGroup):
raise NotImplementedError("Checking if the homomorphism is well-defined "
"is not implemented for permutation groups. Use check=False if you "
"would like to create the homomorphism")

if not isinstance(domain, (PermutationGroup, FpGroup, FreeGroup)):
raise TypeError("The domain must be a group")
if not isinstance(codomain, (PermutationGroup, FpGroup, FreeGroup)):
raise TypeError("The codomain must be a group")
Expand All @@ -199,29 +248,57 @@ def homomorphism(domain, codomain, gens, images=[]):
gens.extend([g for g in generators if g not in gens])
images = dict(zip(gens,images))

if not _check_homomorphism(domain, images, codomain.identity):
if check and not _check_homomorphism(domain, codomain, images):
raise ValueError("The given images do not define a homomorphism")
return GroupHomomorphism(domain, codomain, images)

def _check_homomorphism(domain, images, identity):
def _check_homomorphism(domain, codomain, images):
rels = domain.relators
identity = codomain.identity
def _image(r):
if r.is_identity:
return identity
else:
w = identity
r_arr = r.array_form
i = 0
j = 0
# i is the index for r and j is for
# r_arr. r_arr[j] is the tuple (sym, p)
# where sym is the generator symbol
# and p is the power to which it is
# raised while r[i] is a generator
# (not just its symbol) or the inverse of
# a generator - hence the need for
# both indices
while i < len(r):
power = r_arr[i][1]
power = r_arr[j][1]
if r[i] in images:
w = w*images[r[i]]**power
else:
w = w*images[r[i]**-1]**power
i += abs(power)
j += 1
return w

if any([not _image(r).is_identity for r in rels]):
return False
else:
return True
for r in rels:
if isinstance(codomain, FpGroup):
s = codomain.equals(_image(r), identity)
if s is None:
# only try to make the rewriting system
# confluent when it can't determine the
# truth of equality otherwise
success = codomain.make_confluent()
s = codomain.equals(_image(r), identity)
if s in None and not success:
raise RuntimeError("Can't determine if the images "
"define a homomorphism. Try increasing "
"the maximum number of rewriting rules "
"(group._rewriting_system.set_max(new_value); "
"the current value is stored in group._rewriting"
"_system.maxeqns)")
else:
s = _image(r).is_identity
if not s:
return False
return True

0 comments on commit d4a6198

Please sign in to comment.