Source code for polymerist.maths.fractions.ratios

'''For representing rational numbers, and more general ratios'''

__author__ = 'Timotej Bernat'
__email__ = 'timotej.bernat@colorado.edu'

from dataclasses import dataclass
from typing import Any, Callable, ClassVar, TypeVar
N = TypeVar('N')

from numbers import Number
from math import gcd


# HELPER FUNCTIONS
[docs] def sgnmag(num : N) -> tuple[bool, N]: '''Returns the sign and magnitude of a numeric-like value''' return num < 0, abs(num)
# RATIO CLASSES
[docs] @dataclass(repr=False) class Ratio: '''For representing fractional ratios between two objects''' num : Any denom : Any # REPRESENTATION def __repr__(self) -> str: return f'{self.num}/{self.denom}'
[docs] def to_latex(self) -> str: '''Return latex-compatible string which represent fraction''' return rf'\frac{{{self.num}}}{{{self.denom}}}'
# RELATIONS @property def reciprocal(self) -> 'Ratio': '''Return the reciprocal of a ration''' return self.__class__(self.denom, self.num)
[docs] @dataclass(repr=False) class Rational(Ratio): '''For representing ratios of integers''' num : int denom : int # REDUCTION autoreduce : ClassVar[bool]=False def __post_init__(self) -> None: if self.__class__.autoreduce: self.reduce()
[docs] def reduce(self) -> None: '''Reduce numerator and denominator by greatest common factor''' _gcd = gcd(self.num, self.denom) self.num=int(self.num / _gcd) self.denom=int(self.denom / _gcd)
simplify = reduce # alias for convenience @property def reduced(self) -> 'Rational': '''Return reduced Rational equivalent to the current rational (does not modify in-place)''' new_rat = self.__class__(self.num, self.denom) new_rat.reduce() return new_rat simplifed = reduced # alias for convenience
[docs] def as_proper(self) -> tuple[int, 'Rational']: '''Returns the integer and proper fractional component of a ratio''' integ, remain = divmod(self.num, self.denom) return integ, self.__class__(remain, self.denom)
# ARITHMETIC def __add__(self, other : 'Rational') -> 'Rational': '''Sum of two Rationals''' return self.__class__( num=(self.num * other.denom) + (self.denom * other.num), denom=(self.denom * other.denom) ) def __sub__(self, other : 'Rational') -> 'Rational': '''Difference of two Rationals''' return self.__class__( num=(self.num * other.denom) - (self.denom * other.num), denom=(self.denom * other.denom) ) def __mul__(self, other : 'Rational') -> 'Rational': '''Product of two Rationals''' return self.__class__( num=self.num * other.num, denom=self.denom * other.denom ) def __div__(self, other : 'Rational') -> 'Rational': '''Quotient of two Rationals''' return self.__class__( num=self.num * other.denom, denom=self.denom * other.num ) def __pow__(self, power : float) -> 'Rational': '''Exponentiates a ratio''' return self.__class__( num=self.num**power, denom=self.denom**power )