Source code for pyzeta.core.dynamics.function_systems.moebius_system

"""
Module containing specializations of the abstract base classes `FunctionSystem`
and `HyperbolicMapSystem` to the case of the underlying functions/mappings
being given by Moebius transformations (i.e. real 2x2 matrices with unit
determinant acting on the upper half plane).

Authors:\n
- Philipp Schuette\n
"""

from typing import Tuple

import numpy as np
from numpy import exp

from pyzeta.core.dynamics.function_systems.function_system import (
    FunctionSystem,
)
from pyzeta.core.dynamics.function_systems.helpers.schottky_helper import (
    getDisplacementLengths,
)
from pyzeta.core.dynamics.function_systems.map_system import (
    HyperbolicMapSystem,
)
from pyzeta.core.pyzeta_types.general import (
    tBoolMat,
    tIndexVec,
    tMatVec,
    tVec,
    tWordVec,
)


[docs] class MoebiusFunctionSystem(FunctionSystem): """ Abstract representation of an (iterated) function system the functions of which are given by Moebius transformations. """ __slots__ = ("phi", "_adj")
[docs] def __init__(self, generators: tMatVec, adjacencyMatrix: tBoolMat) -> None: """ Initialize an iterated function system which is more concretely given by iteration of Moebius transformations on the upper half plane. :param generators: array of real 2x2 matrices of non-null determinants :param adjacency matrix: boolean matrix determining legal transitions """ # sanity check that given data is consistent if generators.shape[0] != adjacencyMatrix.shape[0]: raise ValueError( "shape miss-match between generators and adjacency matrix!" ) if adjacencyMatrix.shape[0] != adjacencyMatrix.shape[1]: raise ValueError( "must provide square adjacency matrix for Moebius system!" ) self.phi = generators self._adj = adjacencyMatrix
def _iterateGenerators(self, words: tWordVec) -> tMatVec: """ Iterate the functions defining the system along the individual members of a given array of symbolic words. :param words: array of words whose members are used for iteration :return: array of 2x2 matrices corresponding to the iterates """ # TODO: implement caching to save from re-calculation! self.logger.debug("iterating generators along %s", str(words)) wordNum = words.shape[0] iteratedGenerators = np.empty((wordNum, 2, 2), dtype=np.float64) for i in range(wordNum): temp = np.eye(2, dtype=np.float64) word = words[i] for letter in word: temp = self.phi[letter] @ temp iteratedGenerators[i] = temp self.logger.debug( "iterated generators are %s", str(iteratedGenerators) ) return iteratedGenerators # docstr-coverage: inherited
[docs] def getStabilities(self, words: tWordVec) -> tVec: self.logger.info( "computing stabilities (exponentials of displacement lengths)" ) iteratedGenerators = self._iterateGenerators(words) displacementLens = getDisplacementLengths(iteratedGenerators) self.logger.debug( "calculated displacement lengths %s", str(displacementLens) ) return exp(-displacementLens) # type: ignore
# docstr-coverage: inherited @property def adjacencyMatrix(self) -> tBoolMat: return self._adj
[docs] class MoebiusMapSystem(HyperbolicMapSystem): """ Abstract representation of a hyperbolic map system which is basically given by the inclusion of an additional dimension in a Moebius transformation based iterated function system. """ __slots__ = ("_functionSystem",)
[docs] def __init__(self, functionSystem: MoebiusFunctionSystem) -> None: """ Initialize a hyperbolic map system which delegates most work to an associated Moebius function system. The only required property for subclasses to implement is `fundamentalIntervals`. :param functionSystem: the backing function system """ self._functionSystem = functionSystem
# docstr-coverage: inherited @property def adjacencyMatrix(self) -> tBoolMat: return self._functionSystem.adjacencyMatrix # docstr-coverage: inherited
[docs] def getStabilities(self, words: tWordVec) -> Tuple[tVec, tVec]: contractingStabs = self._functionSystem.getStabilities(words) return contractingStabs, 1 / contractingStabs
[docs] def getGenerators(self, indices: tIndexVec) -> tMatVec: """ Convenience method used to retrieve an array of generators of the Moebius transformation based map system. Used in orbit integral calculation. :param indices: array of indices into the underlying Moebius trafos :return: array of 2x2 matrices representing Moebius trafos """ returnGenerators = np.empty((indices.shape[0], 2, 2), dtype=np.float64) for j, index in enumerate(indices): returnGenerators[j, :, :] = self._functionSystem.phi[index] return returnGenerators
# docstr-coverage: inherited
[docs] def getPeriodicPoints(self, words: tWordVec) -> Tuple[tVec, tVec]: self.logger.info("computing periodic points") iteratedGenerators = self._functionSystem._iterateGenerators(words) # the matrix entry `b` is not required as we assume `c != 0` a = iteratedGenerators[:, 0, 0] c = iteratedGenerators[:, 1, 0] d = iteratedGenerators[:, 1, 1] discriminant = np.sqrt((a + d) ** 2 - 4.0) / (2.0 * c) intermediate = (a - d) / (2.0 * c) fixPts = intermediate + discriminant, intermediate - discriminant # order the fixed points as (repelling, attracting) by selecting... mask = np.array( [ not self._inInterval(lastLetter, secondFixPt) for lastLetter, secondFixPt in zip(words[:, -1], fixPts[1]) ], dtype=np.bool_, ) # .. and then switching selected elements tmp = fixPts[1][mask] fixPts[1][mask] = fixPts[0][mask] fixPts[0][mask] = tmp return fixPts
def _inInterval(self, letter: np.uint8, point: float) -> bool: """ Determine if a given fixed point lies within a fundamental interval. :param letter: symbolic letter fixing the interval :param fixPoint: point whose interval membership is checked """ # TODO: test this thorougly! intervals = self.fundamentalIntervals return intervals[letter][0] <= point <= intervals[letter][1]