"""
A pure Python implementation of TVX.
"""
import bisect
import math
from abc import ABC, ABCMeta, abstractmethod
from typing import Callable, Dict, Iterable, Optional, Union
import numpy as np
from sortedcontainers import SortedList
from tvx.types import BoolOrTVB, FloatOrTVF
[docs]def time() -> 'PyTvf':
"""
A utility function that returns a :py:class:`PyTvf`
whose value at any given time is the time itself.
Returns
-------
PyTvf
A :py:class:`PyTvf` whose value at any given time
is the time itself.
"""
return GlobalTime()
[docs]class PyTvx(ABC):
"""
This is an abstract base class for time-varying
values, such as PyTvf amd PyTvb.
"""
def __repr__(self):
return self.indent_repr()
@abstractmethod
def indent_repr(self, indent: int = 0) -> str:
raise NotImplementedError(str(type(self)) + " is abstract.")
[docs]class PyTvf(PyTvx):
"""
A Time-Varying Floating point (Tvf) value. This is alpha value
that can be turned into alpha float whenever needed, but whose
value as alpha float depends on the global time at which it is
evaluated.
All of the standard operators are available between Tvf's and
other Tvf's or floating point values.
For details and examples, see :ref:`tvx_intro`.
"""
[docs] @abstractmethod
def __call__(self, t: float):
"""
Compute the floating point value of this object at a given time.
Parameters
----------
t
The time at which to evaluate.
Returns
-------
float
The value at time ``t``.
"""
raise NotImplementedError(str(type(self)) + " is abstract.")
def __eq__(self, other: FloatOrTVF) -> 'PyTvb':
return TvbEqualFloats(self, other)
def __ne__(self, other: FloatOrTVF) -> 'PyTvb':
return TvbNotEqualFloats(self, other)
def __lt__(self, other: FloatOrTVF) -> 'PyTvb':
return TvbLessThanFloats(self, other)
def __gt__(self, other: FloatOrTVF) -> 'PyTvb':
return TvbGreaterThanFloats(self, other)
def __le__(self, other: FloatOrTVF) -> 'PyTvb':
return TvbLessEqualFloats(self, other)
def __ge__(self, other: FloatOrTVF) -> 'PyTvb':
return TvbGreaterEqualFloats(self, other)
def __abs__(self) -> 'PyTvf':
return TvfAbs(self)
def __round__(self, n=None) -> 'PyTvf':
return TvfRound(self, n)
def __add__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfAdd(self, other)
def __radd__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfAdd(other, self)
def __sub__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfSub(self, other)
def __rsub__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfSub(other, self)
def __mul__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfMul(self, other)
def __rmul__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfMul(other, self)
def __truediv__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfTrueDiv(self, other)
def __rtruediv__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfTrueDiv(other, self)
def __floordiv__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfFloorDiv(self, other)
def __rfloordiv__(self, other: FloatOrTVF) -> 'PyTvf':
return TvfFloorDiv(other, self)
def __neg__(self) -> 'PyTvf':
return TvfNeg(self)
def __pow__(self, power: FloatOrTVF, modulo=None) -> 'PyTvf':
return TvfPow(self, power, modulo)
[docs]class TvfConst(PyTvf):
"""
A :py:class:`~Tvf` whose value is a constant no matter
what time it is evaluated. This class is rarely constructed
explicitly. Instead, it is normally created implicitly by
an arithmetic expression involving a :py:class:`~Tvf` and a
``float``.
Parameters
----------
x
The value.
"""
def __init__(self, x: float):
super().__init__()
self._x = x
[docs] def __call__(self, t: float) -> float:
return self._x
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "constant: {:f}".format(self._x)
def _const_or_tvf(x: FloatOrTVF) -> PyTvf:
"""
A utility that accepts a ``float`` or a :py:class:`~Tvf` and
returns an equivalent :py:class:`~Tvf`.
Parameters
----------
x
A``float`` or a :py:class:`~Tvf`
Returns
-------
PyTvf
If ``x`` is a float, return a :py:class:`~TvfConst`.
If ``x`` is a :py:class:`~Tvf`, it returns ``x``.
"""
if isinstance(x, PyTvf):
return x
else:
return TvfConst(x)
[docs]class PyTvb(PyTvx):
"""
A Time-Varying Boolean (PyTvb) value. This is alpha value
that can be turned into alpha bool whenever needed, but whose
boolean value depends on the global time at which it is
evaluated.
All of the standard operators are available between Tvb's and
other Tvb's or boolean values.
For details and examples, see :ref:`tvx_intro`.
"""
def __init__(self):
super().__init__()
[docs] @abstractmethod
def __call__(self, t: float) -> bool:
raise NotImplementedError(str(type(self)) + " is abstract.")
def __and__(self, other: BoolOrTVB) -> 'PyTvb':
return TvbOpAnd(self, other)
def __or__(self, other: BoolOrTVB) -> 'PyTvb':
return TvbOpOr(self, other)
def __xor__(self, other: BoolOrTVB) -> 'PyTvb':
return TvbOpXor(self, other)
def __rand__(self, other: BoolOrTVB) -> 'PyTvb':
return TvbOpAnd(other, self)
def __ror__(self, other: BoolOrTVB) -> 'PyTvb':
return TvbOpOr(other, self)
def __rxor__(self, other: BoolOrTVB) -> 'PyTvb':
return TvbOpXor(other, self)
[docs]class GlobalTime(PyTvf):
"""
A Tvf whose value is the time at which it is evaluated.
"""
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "Global Time"
[docs] def __call__(self, t: float) -> float:
return t
def _indent(indent: int):
"""
A utility for producing indents in string representations
of Tvfs.
Parameters
----------
indent
The number of times to indent.
Returns
-------
str
A prefix for a string indented the specified number of times.
"""
return " " * indent
[docs]def float_or_tvx_to_string(x: Union[FloatOrTVF, BoolOrTVB], indent: int) -> str:
"""
Construct a representation of a Tvx at a given level of indentation.
Parameters
----------
x
The tvx.
indent
How many steps to indent.
Returns
-------
str
An indented representation of ``x``.
"""
if isinstance(x, (PyTvf, PyTvb)):
return x.indent_repr(indent)
else:
return _indent(indent) + repr(x)
[docs]class TvbOp(PyTvb):
"""
An abstract base class for a :py:class:`PyTvb` that is the
result of applying a binary operator to two other
:py:class:`PyTvb` instances.
Derived classes fill in the actual boolean operator functionality
as appropriate.
Parameters
----------
lhs
The left-hand side of the binary operator expression.
rhs
The right-hand side of the binary operator expression.
"""
def __init__(self, lhs: BoolOrTVB, rhs: BoolOrTVB):
super().__init__()
self._lhs = _const_or_tvb(lhs)
self._rhs = _const_or_tvb(rhs)
[docs] @abstractmethod
def __call__(self, t: float) -> bool:
raise NotImplementedError(str(type(self)) + " is abstract.")
@classmethod
@abstractmethod
def _op_symbol(cls):
raise NotImplementedError(str(cls) + " is abstract.")
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "operator(" + self._op_symbol() + ")\n" + \
float_or_tvx_to_string(self._lhs, indent + 1) + '\n' + \
float_or_tvx_to_string(self._rhs, indent + 1) + '\n'
[docs]class TvbOpAnd(TvbOp):
"""
A :py:class:`PyTvb` that is the boolean AND
of two others.
"""
@classmethod
def _op_symbol(cls):
return "and"
[docs] def __call__(self, t: float) -> bool:
if self._lhs(t):
return self._rhs(t)
else:
return False
[docs]class TvbOpOr(TvbOp):
"""
A :py:class:`PyTvb` that is the boolean OR
of two others.
"""
@classmethod
def _op_symbol(cls):
return "and"
[docs] def __call__(self, t: float) -> bool:
if self._lhs(t):
return True
else:
return self._rhs(t)
[docs]class TvbOpXor(TvbOp):
"""
A :py:class:`PyTvb` that is the boolean XOR
of two others.
"""
@classmethod
def _op_symbol(cls):
return "^"
[docs] def __call__(self, t: float) -> bool:
return self._lhs(t) ^ self._rhs(t)
[docs]class TvbOpFloats(PyTvb):
"""
An abstract base class for a :py:class:`PyTvb` that is the
result of applying a binary relational operator such as ``>``
or ``==`` to two :py:class:`PyTvf` instances.
Derived classes fill in the actual operator functionality
as appropriate.
Parameters
----------
lhs
The left-hand side of the binary operator expression.
rhs
The right-hand side of the binary operator expression.
"""
def __init__(self, lhs: FloatOrTVF, rhs: FloatOrTVF):
super().__init__()
self._lhs = _const_or_tvf(lhs)
self._rhs = _const_or_tvf(rhs)
[docs] @abstractmethod
def __call__(self, t: float) -> bool:
raise NotImplementedError(str(type(self)) + " is abstract.")
@classmethod
@abstractmethod
def _op_symbol(cls):
raise NotImplementedError(str(cls) + " is abstract.")
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "operator(" + self._op_symbol() + ")\n" + \
float_or_tvx_to_string(self._lhs, indent + 1) + '\n' + \
float_or_tvx_to_string(self._rhs, indent + 1) + '\n'
[docs]class TvbGreaterThanFloats(TvbOpFloats):
"""
A :py:class:`PyTvb` that implements the ``>`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> bool:
return self._lhs(t) > self._rhs(t)
@classmethod
def _op_symbol(cls):
return '>'
[docs]class TvbGreaterEqualFloats(TvbOpFloats):
"""
A :py:class:`PyTvb` that implements the ``>=`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> bool:
return self._lhs(t) >= self._rhs(t)
@classmethod
def _op_symbol(cls):
return '>='
[docs]class TvbLessThanFloats(TvbOpFloats):
"""
A :py:class:`PyTvb` that implements the ``<`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> bool:
return self._lhs(t) < self._rhs(t)
@classmethod
def _op_symbol(cls):
return '<'
[docs]class TvbLessEqualFloats(TvbOpFloats):
"""
A :py:class:`PyTvb` that implements the ``<=`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> bool:
return self._lhs(t) <= self._rhs(t)
@classmethod
def _op_symbol(cls):
return '<='
[docs]class TvbEqualFloats(TvbOpFloats):
"""
A :py:class:`PyTvb` that implements the ``==`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> bool:
return self._lhs(t) == self._rhs(t)
@classmethod
def _op_symbol(cls):
return '=='
[docs]class TvbNotEqualFloats(TvbOpFloats):
"""
A :py:class:`PyTvb` that implements the ``!=`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> bool:
return self._lhs(t) != self._rhs(t)
@classmethod
def _op_symbol(cls):
return '!='
[docs]class TvfRamp(PyTvf):
"""
A :py:class:`~PyTvf` that implements a linear ramp from
an initial value at an initial time to a final value at
a final time.
For details and examples, see :ref:`tvx_intro`.
Parameters
----------
f0
The initial value.
f1
The final value.
x0
This initial time.
width
The width of the ramp. The final time is thus
``x0 + width``.
"""
def __init__(
self,
f0: FloatOrTVF = 0.0,
f1: FloatOrTVF = 1.0,
x0: float = 0.0,
width: float = 1.0
):
super().__init__()
self._f0 = _const_or_tvf(f0)
self._f1 = _const_or_tvf(f1)
self._x0 = float(x0)
self._m = _const_or_tvf((f1 - f0) / width if width > 0.0 else 0.0)
self._w = float(width)
[docs] def __call__(self, t: float) -> float:
x = t - self._x0
if x <= 0.0:
return self._f0(t)
elif x >= self._w:
return self._f1(t)
else:
return (self._f0 + self._m * x)(t)
def indent_repr(self, indent: int = 0) -> str:
return \
_indent(indent) + 'ramp from\n' + \
float_or_tvx_to_string(self._f0, indent + 1) + '\n' + \
_indent(indent) + 'at\n' + \
float_or_tvx_to_string(self._x0, indent + 1) + '\n' + \
_indent(indent) + 'to\n' + \
float_or_tvx_to_string(self._f1, indent + 1) + '\n' + \
_indent(indent) + 'at\n' + \
float_or_tvx_to_string(self._x0 + self._w, indent + 1)
[docs]class TvfOp(PyTvf):
"""
An abstract base class for a :py:class:`PyTvf` that is the
result of applying a binary operator to two other
:py:class:`PyTvf` instances.
Derived classes fill in the actual operator functionality
as appropriate.
Parameters
----------
lhs
The left-hand side of the binary operator expression.
rhs
The right-hand side of the binary operator expression.
"""
def __init__(self, lhs: FloatOrTVF, rhs: FloatOrTVF):
super().__init__()
self._lhs = _const_or_tvf(lhs)
self._rhs = _const_or_tvf(rhs)
@classmethod
@abstractmethod
def _op_symbol(cls):
raise NotImplementedError(str(cls) + " is abstract.")
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "operator(" + self._op_symbol() + ")\n" + \
float_or_tvx_to_string(self._lhs, indent + 1) + '\n' + \
float_or_tvx_to_string(self._rhs, indent + 1)
[docs] @abstractmethod
def __call__(self, t: float) -> float:
raise NotImplementedError(str(type(self)) + " is abstract.")
[docs]class TvfSum(PyTvf):
"""
A :py:class:`~PyTvf` that whose value at any given time is the sum
of the values of a collection of other :py:class:`~PyTvf` instances
at that time.
This class implements an optimization such that if two :py:class:`~PyTvf`
instances are added the result is a new :py:class:`~PyTvf` that sums all
the terms from the two instances.
Parameters
----------
tvf_operands
The values to be summed up.
const_val
An optional constant value added to the sum.
"""
def __init__(self, tvf_operands: Iterable[PyTvf], const_val: float = 0.0):
super().__init__()
self._children = list(tvf_operands)
self._const = const_val
[docs] def __call__(self, t: float) -> float:
return sum([child(t) for child in self._children]) + self._const
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + 'sum(\n' + \
',\n'.join([c.indent_repr(indent + 1) for c in self._children]) + \
',\n' if len(self._children) > 0 else '' + \
_indent(indent + 1) + str(self._const) + \
_indent(indent) + ')\n'
def __add__(self, other: FloatOrTVF) -> 'PyTvf':
if isinstance(other, PyTvf):
return TvfSum(self._children + [other], self._const)
else:
return TvfSum(self._children, self._const + other)
def __radd__(self, other: FloatOrTVF) -> 'PyTvf':
if isinstance(other, PyTvf):
return TvfSum([other] + self._children, self._const)
else:
return TvfSum(self._children, other + self._const)
[docs]class TvfMin(PyTvf):
"""
A :py:class:`~PyTvf` that whose value at any given time is the minimum
of the values of a collection of other :py:class:`~PyTvf` instances
at that time.
Parameters
----------
tvf_operands
The values to be summed up.
"""
def __init__(self, tvf_operands: Iterable[FloatOrTVF]):
super().__init__()
self._children = [_const_or_tvf(op) for op in tvf_operands]
[docs] def __call__(self, t: float) -> float:
return min(*[child(t) for child in self._children])
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + 'max(\n' + \
',\n'.join([c.indent_repr(indent + 1) for c in self._children]) + \
',\n' if len(self._children) > 0 else '' + \
_indent(indent) + ')\n'
[docs]class TvfMax(PyTvf):
"""
A :py:class:`~PyTvf` that whose value at any given time is the maximum
of the values of a collection of other :py:class:`~PyTvf` instances
at that time.
Parameters
----------
tvf_operands
The values to be summed up.
"""
def __init__(self, tvf_operands: Iterable[FloatOrTVF]):
super().__init__()
self._children = [_const_or_tvf(op) for op in tvf_operands]
[docs] def __call__(self, t: float) -> float:
return max(*[child(t) for child in self._children])
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + 'max(\n' + \
',\n'.join([c.indent_repr(indent + 1) for c in self._children]) + \
',\n' if len(self._children) > 0 else '' + \
_indent(indent) + ')\n'
def _min(*args):
if len(args) == 0:
raise ValueError("expected at least 1 argument, got 0")
return TvfMin(args)
def _max(*args):
if len(args) == 0:
raise ValueError("expected at least 1 argument, got 0")
return TvfMax(args)
[docs]class TvfAdd(TvfOp):
"""
A :py:class:`PyTvf` that implements the ``+`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> float:
return self._lhs(t) + self._rhs(t)
def __add__(self, other: FloatOrTVF) -> 'PyTvf':
if isinstance(other, PyTvf):
return TvfSum([self, other])
else:
return TvfSum([self], other)
def __radd__(self, other: FloatOrTVF) -> 'PyTvf':
if isinstance(other, PyTvf):
return TvfSum([other, self])
else:
return TvfSum([self], other)
@classmethod
def _op_symbol(cls):
return '+'
[docs]class TvfSub(TvfOp):
"""
A :py:class:`PyTvf` that implements the ``-`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> float:
return self._lhs(t) - self._rhs(t)
@classmethod
def _op_symbol(cls):
return '-'
[docs]class TvfMul(TvfOp):
"""
A :py:class:`PyTvf` that implements the ``*`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> float:
return self._lhs(t) * self._rhs(t)
@classmethod
def _op_symbol(cls):
return '*'
[docs]class TvfTrueDiv(TvfOp):
"""
A :py:class:`PyTvf` that implements the ``/`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> float:
return self._lhs(t) / self._rhs(t)
@classmethod
def _op_symbol(cls):
return '/'
[docs]class TvfFloorDiv(TvfOp):
"""
A :py:class:`PyTvf` that implements the ``//`` operator
applied to two :py:class:`PyTvf` objects.
"""
[docs] def __call__(self, t: float) -> float:
return self._lhs(t) // self._rhs(t)
@classmethod
def _op_symbol(cls):
return '//'
[docs]class PyTvfUnOp(PyTvf, metaclass=ABCMeta):
"""
An abstract base class for a :py:class:`PyTvf` that is the
result of applying a unary operator to a
:py:class:`PyTvf` instance.
Derived classes fill in the actual operator functionality
as appropriate.
Parameters
----------
x
The value the operator is applied to.
"""
def __init__(self, x: FloatOrTVF):
super().__init__()
self._x = _const_or_tvf(x)
[docs]class TvfNeg(PyTvfUnOp):
"""
A :py:class:`PyTvf` that implements the unary ``-``
operator applied to a :py:class:`PyTvf` object.
"""
[docs] def __call__(self, t: float) -> float:
return -self._x(t)
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + 'negative\n' + \
float_or_tvx_to_string(self._x, indent + 1) + '\n'
[docs]class TvfPow(PyTvf):
"""
A :py:class:`PyTvf` that implements the ``pow``
function applied to two :py:class:`PyTvf` objects.
The value at a given time is the same as the result
of the standard ``pow`` function on the values of ``base``
and ``exp`` at that time.
Parameters
----------
base
The base.
exp
The exponent.
modulo
The modulus.
"""
def __init__(
self,
base: FloatOrTVF,
exp: FloatOrTVF,
modulo: None
):
super().__init__()
self._base = _const_or_tvf(base)
self._exp = _const_or_tvf(exp)
self._modulo = modulo
[docs] def __call__(self, t: float) -> float:
return pow(self._base(t), self._exp(t), self._modulo)
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "pow(\n" + \
float_or_tvx_to_string(self._base, indent + 1) + '\n' + \
float_or_tvx_to_string(self._exp, indent + 1) + '\n' + \
_indent(indent) + ")\n"
[docs]class TvfAbs(PyTvfUnOp):
[docs] def __call__(self, t: float) -> float:
return abs(self._x(t))
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "abs(\n" + \
float_or_tvx_to_string(self._x, indent + 1) + '\n' + \
_indent(indent) + ")\n"
def _tvf_unop(name: str, func: Callable[[float], float]):
class TvfConcreteUnop(PyTvfUnOp):
def __call__(self, t: float) -> float:
return func(self._x(t))
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + name + "(\n" + \
float_or_tvx_to_string(self._x, indent + 1) + '\n' + \
_indent(indent) + ")\n"
def tvf_func(x: PyTvf) -> PyTvf:
return TvfConcreteUnop(x)
return tvf_func
sqrt = _tvf_unop('sqrt', math.sqrt)
sin = _tvf_unop('sin', math.sin)
cos = _tvf_unop('cos', math.cos)
tan = _tvf_unop('tan', math.tan)
asin = _tvf_unop('asin', math.asin)
acos = _tvf_unop('acos', math.acos)
atan = _tvf_unop('atan', math.atan)
sinh = _tvf_unop('sinh', math.sinh)
cosh = _tvf_unop('cosh', math.cosh)
tanh = _tvf_unop('tanh', math.tanh)
asinh = _tvf_unop('asinh', math.asinh)
acosh = _tvf_unop('acosh', math.acosh)
atanh = _tvf_unop('atanh', math.atanh)
[docs]class TvfRound(PyTvf):
def __init__(self, t: PyTvf, n=None):
super().__init__()
self._t = t
self._n = n
[docs] def __call__(self, t: float) -> float:
return float(round(self._t(t), self._n))
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "round(\n" + \
float_or_tvx_to_string(self._t, indent + 1) + ',\n' + \
str(self._n) + \
_indent(indent) + ")"
[docs]class TvfOnce(PyTvf):
def __init__(self, x: PyTvf):
super().__init__()
self._x = x
self._last_time = np.NaN
self._cached_value = 0.0
[docs] def __call__(self, t: float) -> float:
if t != self._last_time:
self._cached_value = self._x(t)
self._last_time = t
return self._cached_value
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + 'Caching value of\n' + float_or_tvx_to_string(self._x, indent + 1)
[docs]def once(x: FloatOrTVF) -> PyTvf:
if isinstance(x, (float, TvfOnce)):
return x
else:
return TvfOnce(x)
[docs]def ramp(
f0: FloatOrTVF = 0.0,
f1: FloatOrTVF = 1.0,
x0: float = 0.0,
width: float = 1.0,
) -> PyTvf:
return TvfRamp(f0, f1, x0, width)
[docs]class TvfCut(PyTvf):
def __init__(
self,
values: Iterable[FloatOrTVF],
cut_times: Iterable[float],
cut_base_time: Optional[PyTvf] = None
):
super().__init__()
if cut_base_time is None:
cut_base_time = time()
self._values = [_const_or_tvf(value) for value in values]
self._cut_times = list(cut_times)
if len(self._values) != len(self._cut_times) + 1:
raise ValueError(
'There must be exactly one more value than cut times. There are {:d} values and {:d} cut times.'.format(
len(self._values), len(self._cut_times)
)
)
self._base_time = cut_base_time
@property
def cut_times(self):
return self._cut_times
@property
def values(self):
return self._values
[docs] def __call__(self, t: float) -> float:
bt = self._base_time(t)
index = bisect.bisect_right(self._cut_times, bt)
return self._values[index](t)
def indent_repr(self, indent: int = 0) -> str:
start = _indent(indent) + "Cut:\n" + _indent(indent + 1) + "(-Inf, "
mid = ''.join(
[
(
"{:.2f}]:\n".format(ct) +
float_or_tvx_to_string(v, indent + 2) +
_indent(indent + 1) + "({:.2f}, ".format(ct)
)
for v, ct in zip(self._values[:-1], self._cut_times)
]
)
end = "+Inf]:\n" + float_or_tvx_to_string(self._values[-1], indent + 2)
return start + mid + end
[docs]def cut(
before: FloatOrTVF,
t: float,
after: FloatOrTVF,
cut_base_time: Optional[PyTvf] = None
):
if isinstance(before, TvfCut):
return TvfCut(before.values + [after], before.cut_times + [t], cut_base_time)
else:
return TvfCut([before, after], [t], cut_base_time)
[docs]class TvbConst(PyTvb):
def __init__(self, x: bool):
super().__init__()
self._x = x
[docs] def __call__(self, t: float) -> bool:
return self._x
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "constant: {:}".format(self._x)
def _const_or_tvb(b: BoolOrTVB) -> PyTvb:
if isinstance(b, PyTvb):
return b
else:
return TvbConst(b)
[docs]def constant(x: Union[int, float, bool]) -> Union[PyTvb, PyTvf]:
if isinstance(x, bool):
return TvbConst(x)
else:
return TvfConst(float(x))
[docs]class IfThenElse(PyTvf):
def __init__(
self,
condition: BoolOrTVB,
true_value: FloatOrTVF = 0.0,
false_value: FloatOrTVF = 0.0
):
super().__init__()
self._condition = _const_or_tvb(condition)
self._true_value = _const_or_tvf(true_value)
self._false_value = _const_or_tvf(false_value)
[docs] def __call__(self, t: float) -> float:
if self._condition(t):
return self._true_value(t)
else:
return self._false_value(t)
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + 'if\n' + \
float_or_tvx_to_string(self._condition, indent + 1) + '\n' + \
_indent(indent) + 'then\n' + \
float_or_tvx_to_string(self._true_value, indent + 1) + '\n' + \
_indent(indent) + 'else\n' + \
float_or_tvx_to_string(self._false_value, indent + 1)
[docs]def if_then_else(
condition: BoolOrTVB,
true_value: FloatOrTVF = 1.0,
false_value: FloatOrTVF = 0.0
) -> PyTvf:
return IfThenElse(condition, true_value, false_value)
[docs]class TvfSample(PyTvf):
def __init__(self, x: PyTvf, sample_times: Optional[Iterable[float]] = None):
super().__init__()
self._x = x
if sample_times is None:
sample_times = []
self._sample_times: SortedList = SortedList(sample_times)
self._samples: Dict[float, float] = {}
def _sample(self, t: float) -> float:
if t not in self._samples:
v = self._x(t)
self._samples[t] = v
return self._samples[t]
[docs] def __call__(self, t: float) -> float:
ii = bisect.bisect_right(self._sample_times, t)
# On either end, we just extrapolate the first
# or last value.
if ii == 0:
return self._sample(self._sample_times[0])
elif ii == len(self._sample_times):
return self._sample(self._sample_times[-1])
t0 = self._sample_times[ii - 1]
t1 = self._sample_times[ii]
x0 = self._sample(t0)
x1 = self._sample(t1)
s = (t - t0) / (t1 - t0)
return (1 - s) * x0 + s * x1
def indent_repr(self, indent: int = 0) -> str:
return _indent(indent) + "Sampler over\n" + self._x.indent_repr(indent + 1)
[docs]def sample(x: PyTvf, sample_times: Iterable[float]):
return TvfSample(x, sample_times)