Source code for abjad.ratio

import collections
import typing

import quicktions

from . import math
from .duration import Multiplier
from .storage import FormatSpecification, StorageFormatManager


class NonreducedRatio(collections.abc.Sequence):
    """
    Nonreduced ratio.

    ..  container:: example

        Initializes from numbers:

        >>> abjad.NonreducedRatio((2, 4))
        NonreducedRatio((2, 4))

        >>> abjad.NonreducedRatio((2, 4, 2))
        NonreducedRatio((2, 4, 2))

    ..  container:: example

        Initializes from string:

        >>> abjad.NonreducedRatio("2:4")
        NonreducedRatio((2, 4))

        >>> abjad.NonreducedRatio("2:4:2")
        NonreducedRatio((2, 4, 2))

    """

    ### CLASS VARIABLES ###

    __slots__ = ("_numbers",)

    ### INITIALIZER ###

    def __init__(self, numbers=(1, 1)):
        if isinstance(numbers, type(self)):
            numbers = numbers.numbers
        elif isinstance(numbers, str):
            strings = numbers.split(":")
            numbers = [int(_) for _ in strings]
        numbers = tuple(numbers)
        self._numbers = numbers

    ### SPECIAL METHODS ###

    def __contains__(self, argument):
        """
        Is true when ratio contains ``argument``.

        Returns true or false.
        """
        return argument in self._numbers

    def __eq__(self, argument):
        """
        Is true when ``argument`` is a nonreduced ratio with numerator and
        denominator equal to those of this nonreduced ratio.

        Returns true or false.
        """
        return StorageFormatManager.compare_objects(self, argument)

    def __getitem__(self, argument):
        """
        Gets item or slice identified by ``argument``.

        ..  container:: example

            >>> ratio = abjad.NonreducedRatio((2, 4, 2))
            >>> ratio[1]
            4

        Returns integer or tuple.
        """
        if isinstance(argument, slice):
            return tuple(self._numbers.__getitem__(argument))
        return self._numbers.__getitem__(argument)

    def __hash__(self):
        """
        Hashes non-reduced ratio.

        Required to be explicitly redefined on Python 3 if __eq__ changes.

        Returns integer.
        """
        return super().__hash__()

    def __iter__(self):
        """
        Iterates ratio.

        Returns generator.
        """
        return iter(self._numbers)

    def __len__(self):
        """
        Gets length of ratio.

        ..  container:: example

            >>> ratio = abjad.NonreducedRatio((2, 4, 2))
            >>> len(ratio)
            3

        Returns integer.
        """
        return len(self._numbers)

    def __repr__(self) -> str:
        """
        Gets interpreter representation.
        """
        return StorageFormatManager(self).get_repr_format()

    def __reversed__(self):
        """
        Iterates ratio in reverse.

        Returns generator.
        """
        return reversed(self._numbers)

    def __rtruediv__(self, number):
        """
        Divides ``number`` by ratio.

        ..  container:: example

            >>> 1 / abjad.Ratio((1, 1, 3))
            [Fraction(1, 5), Fraction(1, 5), Fraction(3, 5)]

        ..  container:: example

            >>> 1 / abjad.Ratio((1, 1, 3))
            [Fraction(1, 5), Fraction(1, 5), Fraction(3, 5)]

        ..  container:: example

            >>> 1.0 / abjad.Ratio((1, 1, 3))
            [0.2, 0.2, 0.6]

        Returns list of fractions or list of floats.
        """
        denominator = sum(self.numbers)
        factors = [quicktions.Fraction(_, denominator) for _ in self.numbers]
        result = [_ * number for _ in factors]
        return result

    __rdiv__ = __rtruediv__

    ### PRIVATE METHODS ###

    def _get_format_specification(self):
        return FormatSpecification(
            client=self,
            storage_format_args_values=[self.numbers],
            storage_format_is_indented=False,
            storage_format_keyword_names=[],
        )

    ### PUBLIC PROPERTIES ###

    @property
    def multipliers(self):
        """
        Gets multipliers of nonreduced ratio.

        ..  container:: example

            Nonreduced ratio of two numbers:

            >>> ratio = abjad.NonreducedRatio((2, 4))
            >>> ratio.multipliers
            (Multiplier(1, 3), Multiplier(2, 3))

        ..  container:: example

            Nonreduced ratio of three numbers:

            >>> ratio = abjad.NonreducedRatio((2, 4, 2))
            >>> ratio.multipliers
            (Multiplier(1, 4), Multiplier(1, 2), Multiplier(1, 4))

        Returns tuple of multipliers.
        """
        weight = sum(self.numbers)
        multipliers = [Multiplier((_, weight)) for _ in self.numbers]
        multipliers = tuple(multipliers)
        return multipliers

    @property
    def numbers(self):
        """
        Gets numbers of nonreduced ratio.

        ..  container:: example

            Nonreduced ratio of two numbers:

            >>> ratio = abjad.NonreducedRatio((2, 4))
            >>> ratio.numbers
            (2, 4)

        ..  container:: example

            Nonreduced ratio of three numbers:

            >>> ratio = abjad.NonreducedRatio((2, 4, 2))
            >>> ratio.numbers
            (2, 4, 2)

        Set to tuple of two or more numbers.

        Returns tuple of two or more numbers.
        """
        return self._numbers

    ### PUBLIC METHODS ###

    def count(self, argument):
        """
        Gets count of ``argument`` in ratio.

        Returns integer.
        """
        return self._numbers.count(argument)

    def index(self, argument):
        """
        Gets index of ``argument`` in ratio.

        Returns integer.
        """
        return self._numbers.index(argument)


class Ratio(NonreducedRatio):
    """
    Ratio.

    ..  container:: example

        Initializes from numbers:

        >>> abjad.Ratio((2, 4))
        Ratio((1, 2))

        >>> abjad.Ratio((2, 4, 2))
        Ratio((1, 2, 1))

    ..  container:: example

        Initializes from string:

        >>> abjad.Ratio("2:4")
        Ratio((1, 2))

        >>> abjad.Ratio("2:4:2")
        Ratio((1, 2, 1))

    """

    ### CLASS VARIABLES ###

    __slots__ = ()

    ### INITIALIZER ###

    def __init__(self, numbers=(1, 1)):
        if isinstance(numbers, type(self)):
            numbers = numbers.numbers
        elif isinstance(numbers, str):
            strings = numbers.split(":")
            numbers = [int(_) for _ in strings]
        numbers = [int(_) for _ in numbers]
        gcd = math.greatest_common_divisor(*numbers)
        numbers = [_ // gcd for _ in numbers]
        self._numbers = tuple(numbers)

    ### SPECIAL METHODS ###

[docs] def __eq__(self, argument): """ Is true when ``argument`` equals ratio. .. container:: example >>> ratio_1 = abjad.Ratio((1, 2, 1)) >>> ratio_2 = abjad.Ratio((1, 2, 1)) >>> ratio_3 = abjad.Ratio((2, 3, 3)) >>> ratio_1 == ratio_1 True >>> ratio_1 == ratio_2 True >>> ratio_1 == ratio_3 False >>> ratio_2 == ratio_1 True >>> ratio_2 == ratio_2 True >>> ratio_2 == ratio_3 False >>> ratio_3 == ratio_1 False >>> ratio_3 == ratio_2 False >>> ratio_3 == ratio_3 True """ return super().__eq__(argument)
[docs] def __getitem__(self, argument): """ Gets item or slice identified by ``argument``. .. container:: example >>> ratio = abjad.Ratio((2, 4, 2)) >>> ratio[1] 2 Returns integer or tuple. """ if isinstance(argument, slice): return tuple(self._numbers.__getitem__(argument)) return self._numbers.__getitem__(argument)
[docs] def __hash__(self): """ Hashes ratio. Required to be explicitly redefined on Python 3 if __eq__ changes. Returns integer. """ return super().__hash__()
[docs] def __len__(self): """ Gets length of ratio. .. container:: example >>> ratio = abjad.Ratio((2, 4, 2)) >>> len(ratio) 3 Returns integer. """ return len(self._numbers)
[docs] def __str__(self): """ Gets string representation of ratio. .. container:: example Ratio of two numbers: >>> str(abjad.Ratio((2, 4))) '1:2' .. container:: example Ratio of three numbers: >>> str(abjad.Ratio((2, 4, 2))) '1:2:1' Returns string. """ numbers = (str(x) for x in self.numbers) return ":".join(numbers)
### PUBLIC PROPERTIES ### @property def multipliers(self): """ Gets multipliers of ratio. .. container:: example Ratio of two numbers: >>> ratio = abjad.Ratio((2, 4)) >>> ratio.multipliers (Multiplier(1, 3), Multiplier(2, 3)) .. container:: example Ratio of three numbers: >>> ratio = abjad.Ratio((2, 4, 2)) >>> ratio.multipliers (Multiplier(1, 4), Multiplier(1, 2), Multiplier(1, 4)) Returns tuple of multipliers. """ weight = sum(self.numbers) multipliers = [Multiplier((_, weight)) for _ in self.numbers] multipliers = tuple(multipliers) return multipliers @property def numbers(self): """ Gets numbers of ratio. .. container:: example Ratio of two numbers: >>> ratio = abjad.Ratio((2, 4)) >>> ratio.numbers (1, 2) .. container:: example Ratio of three numbers: >>> ratio = abjad.Ratio((2, 4, 2)) >>> ratio.numbers (1, 2, 1) Set to tuple of two or more numbers. Returns tuple of two or more numbers. """ return self._numbers @property def reciprocal(self): """ Gets reciprocal. .. container:: example Gets reciprocal: >>> abjad.Ratio((3, 2)).reciprocal Ratio((2, 3)) >>> abjad.Ratio((3, 2, 7)).reciprocal Ratio((7, 2, 3)) Returns new ratio. """ numbers = list(reversed(self.numbers)) return type(self)(numbers) ### PUBLIC METHODS ###
[docs] def partition_integer(self, n) -> typing.List[int]: """ Partitions positive integer-equivalent ``n`` by ``ratio``. .. container:: example >>> abjad.Ratio([1, 2]).partition_integer(10) [3, 7] .. container:: example Partitions positive integer-equivalent ``n`` by ``ratio`` with negative parts: >>> abjad.Ratio([1, -2]).partition_integer(10) [3, -7] .. container:: example Partitions negative integer-equivalent ``n`` by ``ratio``: >>> abjad.Ratio([1, 2]).partition_integer(-10) [-3, -7] .. container:: example Partitions negative integer-equivalent ``n`` by ``ratio`` with negative parts: >>> abjad.Ratio([1, -2]).partition_integer(-10) [-3, 7] .. container:: example More examples: >>> abjad.Ratio([1]).partition_integer(10) [10] >>> abjad.Ratio([1, 1]).partition_integer(10) [5, 5] >>> abjad.Ratio([1, -1, -1]).partition_integer(10) [3, -4, -3] >>> abjad.Ratio([1, 1, 1, 1]).partition_integer(-10) [-3, -2, -3, -2] >>> abjad.Ratio([1, 1, 1, 1, 1]).partition_integer(-10) [-2, -2, -2, -2, -2] Returns result with weight equal to absolute value of ``n``. """ if not math.is_integer_equivalent_number(n): raise TypeError(f"is not integer-equivalent number: {n!r}.") ratio = self.numbers if not all(math.is_integer_equivalent_number(part) for part in ratio): message = f"some parts in {ratio!r} not integer-equivalent numbers." raise TypeError(message) result = [0] divisions = [float(abs(n)) * abs(part) / math.weight(ratio) for part in ratio] cumulative_divisions = math.cumulative_sums(divisions, start=None) for division in cumulative_divisions: rounded_division = int(round(division)) - sum(result) if division - round(division) == 0.5: rounded_division += 1 result.append(rounded_division) result = result[1:] if math.sign(n) == -1: result = [-x for x in result] ratio_signs = [math.sign(x) for x in ratio] result = [pair[0] * pair[1] for pair in zip(ratio_signs, result)] return result