Source code for bag.util.math

# SPDX-License-Identifier: BSD-3-Clause AND Apache-2.0
# Copyright 2018 Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Copyright 2019 Blue Cheetah Analog Design Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from typing import Any, Mapping

import ast
import operator
from math import trunc, ceil, floor
from numbers import Integral, Real


[docs]class HalfInt(Integral): """A class that represents a half integer.""" def __init__(self, dbl_val: Any) -> None: if isinstance(dbl_val, Integral): self._val = int(dbl_val) else: raise ValueError('HalfInt internal value must be an integer.') @classmethod
[docs] def convert(cls, val: Any) -> HalfInt: if isinstance(val, HalfInt): return val elif isinstance(val, Integral): return HalfInt(2 * int(val)) elif isinstance(val, Real): tmp = float(2 * val) if tmp.is_integer(): return HalfInt(int(tmp)) raise ValueError('Cannot convert {} type {} to HalfInt.'.format(val, type(val)))
@property
[docs] def value(self) -> float: q, r = divmod(self._val, 2) return q if r == 0 else q + 0.5
@property
[docs] def is_integer(self) -> bool: return self._val % 2 == 0
@property
[docs] def dbl_value(self) -> int: return self._val
[docs] def div2(self, round_up: bool = False) -> HalfInt: q, r = divmod(self._val, 2) return HalfInt(q + (r and round_up))
[docs] def to_string(self) -> str: q, r = divmod(self._val, 2) if r == 0: return '{:d}'.format(q) return '{:d}.5'.format(q)
[docs] def up(self) -> HalfInt: return HalfInt(self._val + 1)
[docs] def down(self) -> HalfInt: return HalfInt(self._val - 1)
[docs] def up_even(self, flag: bool) -> HalfInt: return HalfInt(self._val + (self._val & flag))
[docs] def down_even(self, flag: bool) -> HalfInt: return HalfInt(self._val - (self._val & flag))
[docs] def __str__(self): return repr(self)
[docs] def __repr__(self): return 'HalfInt({})'.format(self._val / 2)
[docs] def __hash__(self): return hash(self._val / 2)
[docs] def __eq__(self, other): if isinstance(other, HalfInt): return self._val == other._val return self._val == 2 * other
[docs] def __ne__(self, other): return not (self == other)
[docs] def __le__(self, other): if isinstance(other, HalfInt): return self._val <= other._val return self._val <= 2 * other
[docs] def __lt__(self, other): if isinstance(other, HalfInt): return self._val < other._val return self._val < 2 * other
[docs] def __ge__(self, other): return not (self < other)
[docs] def __gt__(self, other): return not (self <= other)
[docs] def __add__(self, other): other = HalfInt.convert(other) return HalfInt(self._val + other._val)
[docs] def __sub__(self, other): return self + (-other)
[docs] def __mul__(self, other): other = HalfInt.convert(other) q, r = divmod(self._val * other._val, 2) if r == 0: return HalfInt(q) raise ValueError('result is not a HalfInt.')
[docs] def __truediv__(self, other): other = HalfInt.convert(other) q, r = divmod(2 * self._val, other._val) if r == 0: return HalfInt(q) raise ValueError('result is not a HalfInt.')
[docs] def __floordiv__(self, other): other = HalfInt.convert(other) return HalfInt(2 * (self._val // other._val))
[docs] def __mod__(self, other): other = HalfInt.convert(other) return HalfInt(self._val % other._val)
[docs] def __divmod__(self, other): other = HalfInt.convert(other) q, r = divmod(self._val, other._val) return HalfInt(2 * q), HalfInt(r)
[docs] def __pow__(self, other, modulus=None): other = HalfInt.convert(other) if self.is_integer and other.is_integer: return HalfInt(2 * (self._val // 2)**(other._val // 2)) raise ValueError('result is not a HalfInt.')
[docs] def __lshift__(self, other): raise TypeError('Cannot lshift HalfInt')
[docs] def __rshift__(self, other): raise TypeError('Cannot rshift HalfInt')
[docs] def __and__(self, other): raise TypeError('Cannot and HalfInt')
[docs] def __xor__(self, other): raise TypeError('Cannot xor HalfInt')
[docs] def __or__(self, other): raise TypeError('Cannot or HalfInt')
[docs] def __radd__(self, other): return self + other
[docs] def __rsub__(self, other): return (-self) + other
[docs] def __rmul__(self, other): return self * other
[docs] def __rtruediv__(self, other): return HalfInt.convert(other) / self
[docs] def __rfloordiv__(self, other): return HalfInt.convert(other) // self
[docs] def __rmod__(self, other): return HalfInt.convert(other) % self
[docs] def __rdivmod__(self, other): return HalfInt.convert(other).__divmod__(self)
[docs] def __rpow__(self, other): return HalfInt.convert(other)**self
[docs] def __rlshift__(self, other): raise TypeError('Cannot lshift HalfInt')
[docs] def __rrshift__(self, other): raise TypeError('Cannot rshift HalfInt')
[docs] def __rand__(self, other): raise TypeError('Cannot and HalfInt')
[docs] def __rxor__(self, other): raise TypeError('Cannot xor HalfInt')
[docs] def __ror__(self, other): raise TypeError('Cannot or HalfInt')
[docs] def __iadd__(self, other): return self + other
[docs] def __isub__(self, other): return self - other
[docs] def __imul__(self, other): return self * other
[docs] def __itruediv__(self, other): return self / other
[docs] def __ifloordiv__(self, other): return self // other
[docs] def __imod__(self, other): return self % other
[docs] def __ipow__(self, other): return self ** other
[docs] def __ilshift__(self, other): raise TypeError('Cannot lshift HalfInt')
[docs] def __irshift__(self, other): raise TypeError('Cannot rshift HalfInt')
[docs] def __iand__(self, other): raise TypeError('Cannot and HalfInt')
[docs] def __ixor__(self, other): raise TypeError('Cannot xor HalfInt')
[docs] def __ior__(self, other): raise TypeError('Cannot or HalfInt')
[docs] def __neg__(self): return HalfInt(-self._val)
[docs] def __pos__(self): return HalfInt(self._val)
[docs] def __abs__(self): return HalfInt(abs(self._val))
[docs] def __invert__(self): return -self
[docs] def __complex__(self): raise TypeError('Cannot cast to complex')
[docs] def __int__(self): if self._val % 2 == 1: raise ValueError('Not an integer.') return self._val // 2
[docs] def __float__(self): return self._val / 2
[docs] def __index__(self): return int(self)
[docs] def __round__(self, ndigits=0): if self.is_integer: return HalfInt(self._val) else: return HalfInt(round(self._val / 2) * 2)
[docs] def __trunc__(self): if self.is_integer: return HalfInt(self._val) else: return HalfInt(trunc(self._val / 2) * 2)
[docs] def __floor__(self): if self.is_integer: return HalfInt(self._val) else: return HalfInt(floor(self._val / 2) * 2)
[docs] def __ceil__(self): if self.is_integer: return HalfInt(self._val) else: return HalfInt(ceil(self._val / 2) * 2)
# noinspection PyPep8Naming,PyMethodMayBeStatic
[docs]class Calculator(ast.NodeVisitor): """A simple calculator. Modified from: https://stackoverflow.com/questions/33029168/how-to-calculate-an-equation-in-a-string-python user mgilson said in a comment that he agrees to distribute code with Apache 2.0 license. """
[docs] _OP_MAP = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Invert: operator.neg, ast.FloorDiv: operator.floordiv, ast.USub: operator.neg,
} def __init__(self, namespace: Mapping[str, Any]) -> None: super().__init__() self._calc_namespace = namespace
[docs] def __getitem__(self, name: str) -> Any: return self._calc_namespace[name]
@property
[docs] def namespace(self) -> Mapping[str, Any]: return self._calc_namespace
[docs] def visit_BinOp(self, node): left = self.visit(node.left) right = self.visit(node.right) return self._OP_MAP[type(node.op)](left, right)
[docs] def visit_UnaryOp(self, node): operand = self.visit(node.operand) return self._OP_MAP[type(node.op)](operand)
[docs] def visit_Num(self, node): return node.n
[docs] def visit_Expr(self, node): return self.visit(node.value)
[docs] def visit_Name(self, node): return self._calc_namespace[node.id]
[docs] def eval(self, expression: str): tree = ast.parse(expression) return self.visit(tree.body[0])
@classmethod
[docs] def evaluate(cls, expr: str, namespace: Mapping[str, Any]): return cls(namespace).eval(expr)