Skip to content
113 changes: 102 additions & 11 deletions src/sage/combinat/words/morphism.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,10 +940,22 @@ def __mul__(self, other):
sage: WordMorphism('')*m
Traceback (most recent call last):
...
KeyError: 'b'
ValueError: the codomain alphabet of the second morphism must be included in the domain alphabet of the first morphism with the same ordering
sage: m * WordMorphism('')
WordMorphism:
"""
sage: s = WordMorphism('a->ba,b->a', domain=Words('ab'), codomain=Words('ba'))
sage: s * s
Traceback (most recent call last):
...
ValueError: the codomain alphabet of the second morphism must be included in the domain alphabet of the first morphism with the same ordering
"""
# Check that other's codomain alphabet is included in self's domain alphabet
# with the correct ordering
if not self._is_alphabet_included_with_order(
other.codomain().alphabet(), self.domain().alphabet()):
raise ValueError("the codomain alphabet of the second morphism must be "
"included in the domain alphabet of the first morphism "
"with the same ordering")
return WordMorphism({key: self(w) for key, w in other._morph.items()},
codomain=self.codomain())

Expand Down Expand Up @@ -982,7 +994,7 @@ def __pow__(self, exp):
sage: n^2
Traceback (most recent call last):
...
KeyError: 'c'
ValueError: the codomain alphabet of the second morphism must be included in the domain alphabet of the first morphism with the same ordering
"""
# If exp is not an integer
if not isinstance(exp, (int, Integer)):
Expand Down Expand Up @@ -1203,9 +1215,88 @@ def is_endomorphism(self):
"""
return self.codomain() == self.domain()

def _is_alphabet_included_with_order(self, source_alphabet, target_alphabet):
"""
Check if ``source_alphabet`` is included in ``target_alphabet`` with the correct ordering.

This is a helper function for checking composition validity.
For composition ``self * other`` to preserve the incidence matrix
property, we need ``other.codomain().alphabet()`` to be included
in ``self.domain().alphabet()`` with matching order.

INPUT:

- ``source_alphabet`` -- the alphabet to check for inclusion
- ``target_alphabet`` -- the alphabet to check inclusion into

OUTPUT:

``True`` if all letters of ``source_alphabet`` appear in
``target_alphabet`` in the same relative order, ``False`` otherwise.

EXAMPLES::

sage: m = WordMorphism('a->ab,b->a')
sage: m._is_alphabet_included_with_order(
....: Words('ab').alphabet(), Words('ab').alphabet())
True
sage: m._is_alphabet_included_with_order(
....: Words('ab').alphabet(), Words('abc').alphabet())
True
sage: m._is_alphabet_included_with_order(
....: Words('ba').alphabet(), Words('ab').alphabet())
False
sage: m._is_alphabet_included_with_order(
....: Words('ba').alphabet(), Words('abc').alphabet())
False
sage: m._is_alphabet_included_with_order(
....: Words('ac').alphabet(), Words('abc').alphabet())
True
sage: m._is_alphabet_included_with_order(
....: Words('abc').alphabet(), Words('abc').alphabet())
True
sage: m._is_alphabet_included_with_order(
....: Words('abc').alphabet(), Words('ab').alphabet())
False
sage: m._is_alphabet_included_with_order(
....: Words('').alphabet(), Words('ab').alphabet())
True
"""
# Check if alphabets are equal first (fast path)
if source_alphabet == target_alphabet:
return True

# Check cardinality constraints
if target_alphabet.cardinality() < source_alphabet.cardinality():
return False

if target_alphabet.cardinality() == Infinity:
raise NotImplementedError("cannot check alphabet inclusion for infinite alphabets")

# Check that all letters in source_alphabet are in target_alphabet
source_list = list(source_alphabet)
target_list = list(target_alphabet)

if not all(a in target_alphabet for a in source_list):
return False

# Check that the relative order is preserved
# Find positions of source letters in target
target_positions = {letter: i for i, letter in enumerate(target_list)}
source_positions = [target_positions[letter] for letter in source_list]

# Check if positions are in increasing order
return all(source_positions[i] < source_positions[i+1]
for i in range(len(source_positions)-1))

def is_self_composable(self):
r"""
Return whether the codomain of ``self`` is contained in the domain.
Return whether the codomain of ``self`` is contained in the domain
with the correct ordering.

For a morphism to be self-composable (i.e., ``self * self`` to be valid),
the codomain alphabet must be included in the domain alphabet with the
same relative ordering of letters.

EXAMPLES::

Expand All @@ -1214,16 +1305,16 @@ def is_self_composable(self):
False
sage: f.is_self_composable()
True

Check that alphabet ordering matters::

sage: s = WordMorphism('a->ba,b->a', domain=Words('ab'), codomain=Words('ba'))
sage: s.is_self_composable()
False
"""
Adom = self.domain().alphabet()
Acodom = self.codomain().alphabet()
if Adom == Acodom:
return True
if Adom.cardinality() < Acodom.cardinality():
return False
if Adom.cardinality() == Infinity:
raise NotImplementedError
return all(a in Adom for a in Acodom)
return self._is_alphabet_included_with_order(Acodom, Adom)

def image(self, letter):
r"""
Expand Down
Loading