## Basic representations for datatypes,
## These are mainly used for typehinting within in the module to prevent circular import messes
## Actual methods for these classes are found in core/numbers.py
from __future__ import annotations
from ._abc import BasicNode, BasicExpression, BasicFunction, BasicVariable
from abc import abstractmethod
import numbers
from typing import Any, Callable, List, TypeVar, Union
import cake
U = TypeVar('U')
F = TypeVar('F')
N = TypeVar('N', complex, float, int, str)
[docs]class Number(BasicNode, numbers.Number):
''' Represents a basic number '''
value: Any
def __init__(self, __value: N, /) -> None:
self.__value = __value
super().__init__()
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.value})'
@property
def value(self) -> Any:
return self.__value
@value.setter
def check_before_set(self, new: Any) -> None:
if hasattr(self, '_type'):
assert isinstance(new, self._type), 'Invalid value set'
self.__value = new
[docs]class Variable(BasicVariable, BasicNode):
''' Represents an Variable value,
this can be used inplace of any integer throughout the cake library.
.. note::
Where a specific type is required,
having a default value is preferable
.. rubric:: Solving for when 'a' is known
.. code-block:: py
>>> a = Variable('a')
>>> expr = (a + 5) / 2
>>> expr.solve() == expr
True
>>> expr.solve(a=9)
Real(7.0)
.. rubric:: Solving for 'a' when result is known
.. code-block:: py
>>> expr
Expression((a + 5) / 2)
>>> equation = expr.to_equation(right=10)
# So we now have an equation where (a + 5) / 2 = 10
>>> equation *= 2
# Equivalent to multiplying both sides by 2
# a + 5 = 20
>>> equation -= 5
# a = 15
>>> equation.result(as_unknown=True)
Variable('a', default_value=15)
'''
representation: str
coefficient: Any
power: Any
def __init__(self, repr: str, coefficient: Any = 1, power: Any = 1) -> None:
self.__repr = repr
self.coefficient = coefficient
self.power = power
def __str__(self) -> str:
return f'{self.coefficient if self.coefficient != 1 else ""}{self.representation}{f"**{self.power}" if self.power != 1 else ""}'
def __repr__(self) -> str:
return f'Variable(\'{self.representation}\', coefficient={self.coefficient}, power={self.power})'
@property
def representation(self) -> str:
return self.__repr
@representation.setter
def set_repr(self, new: str) -> None:
self.__repr = str(new)
[docs] @classmethod
def many(cls, *symbols) -> List[Variable]:
''' Returns a list of Variables from the input given
.. code-block:: py
>>> Variable.many('x', 'y')
[Variable('x'), Variable('y')]
>>> Variable.many(('x', 5), ('y', 1, 2), 'z')
[Variable('x', coefficient=5), Variable('y', coefficient=1, power=2), Variable('z')]
'''
return [cls(i) if isinstance(i, str) else cls(*i) for i in symbols]
[docs]class Function(BasicFunction, BasicNode):
''' Base class for creating functions,
behaves similarly to an Variable in the sense the value of the function is not calcuated until called.
This feature allows it to intake Variables as values.
'''
coefficient: Any
''' Functions coefficient '''
power: Any
''' Power function is raised to '''
parameter: Any
''' Internal parameter of function '''
auto_to_radians: bool
''' Convert value to radians before passing through function '''
auto_preprocess: bool
''' Whether to automatically preprocess value '''
preprocessor: Callable[[dict], dict]
''' preprocessor function, the value passed is the dictionary of given values,
must return a new mapping of values to use instead.
'''
auto_postprocess: bool
''' Whether to automatically post process value '''
postprocessor: Callable[[Any], Any]
''' postprocessor function '''
auto_prehandle: bool
''' Whether to automatically pre-handle a value,
this is right before the value is passed through the handler
.. warning::
this is after the value is converted to radians! '''
prehandler: Callable[[Any], Any]
''' prehandler function '''
def __init__(self, parameter: Any, coefficient: Any = 1, power: Any = 1) -> None:
self.parameter = parameter if not isinstance(parameter, cake.Operation) else cake.Expression(parameter)
self.coefficient = coefficient if not isinstance(coefficient, cake.Operation) else cake.Expression(coefficient)
self.power = power if not isinstance(power, cake.Operation) else cake.Expression(power)
self.auto_to_radians = False
self.auto_preprocess = False
self.preprocessor = None
self.auto_postprocess = False
self.postprocessor = None
self.auto_prehandle = False
self.prehandler = None
[docs] def copy(self) -> Function:
''' Returns a shallow copy of the function '''
f = self.__class__(self.parameter, self.coefficient, self.power)
f.auto_to_radians = self.auto_to_radians
f.auto_preprocess = self.auto_preprocess
f.preprocessor = self.preprocessor
f.auto_postprocess = self.auto_postprocess
f.postprocessor = self.postprocessor
f.auto_prehandle = self.auto_prehandle
f.prehandler = self.prehandler
return f
[docs] @abstractmethod
def evaluate(self, /, to_radians: bool = False,
use_preprocess: bool = False,
use_postprocess: bool = False,
use_prehandler: bool = False,
**kwds) -> Any:
''' Evaluates the function returning either an updated version of the parameter or a value.
>>> F = Function(Expression(Add(3, 'x')))
>>> F
Function(3 + x)
>>> F.evaluate(x=3)
Function(3 + 3) ## Result is returned here
>>> F.evaluate()
Function(3 + x) ## Function is returned as x is still Variable
'''
@property
def name(self) -> str:
return self.__class__.__name__
OtherType = Union[Number, Variable, BasicExpression, numbers.Number, Function]
[docs]class Complex(Number):
''' Represents a complex number '''
value: complex
def __init__(self, real: N, imag: N = 0, /) -> None:
self._Number__value = complex(real, imag)
[docs]class Real(Complex):
''' Represents a real/float '''
value: float
def __init__(self, value: N, /) -> None:
self._Number__value = float(value)
[docs] def to_rational(self) -> Rational:
return Rational(self.value)
[docs]class Rational(Real):
''' Represents a rational number '''
value: float
numerator: Number
denominator: Number
def __init__(self, value_or_numerator, denominator = None) -> None:
if not denominator:
value_or_numerator = float(value_or_numerator)
self._Number__value = value_or_numerator
self.numerator, self.denominator = value_or_numerator
else:
self._Number__value = value_or_numerator / denominator
self.numerator = value_or_numerator
self.denominator = denominator
[docs]class Integral(Rational):
''' Represents a integral number '''
value: int
def __init__(self, value: int) -> None:
self._Number__value = int(value)