Source code for avl._core.float

# Copyright 2024 Apheleia
#
# Description:
# Apheleia Verification Library Variable Class

from __future__ import annotations

import warnings
from collections.abc import Callable
from typing import Any

import numpy as np
from z3 import And, Real

from .var import Var


[docs] class Fp16(Var):
[docs] def __init__(self, name: str, value: float, auto_random: bool = True, fmt: Callable[..., float] = str) -> None: """ Initialize an instance of the class. :param name: The name of the instance. :type name: str :param value: The value to be assigned to the instance. :type value: int :param auto_random: Flag to enable automatic randomization, defaults to True. :type auto_random: bool, optional :param fmt: The format to be used, defaults to hex. :type fmt: function, optional """ if not hasattr(self, "value"): self.value = np.float16(0) self._bits_ = np.uint16(0) super().__init__(name, value, auto_random=auto_random, fmt=fmt)
def _cast_(self, other: Any) -> Any: """ Cast the other value to the type of this variable's value. :param other: The value to cast. :type other: Any :return: The casted value. :rtype: Any """ with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=RuntimeWarning, message="overflow encountered in cast") return super()._cast_(other) def _range_(self) -> tuple[int, int]: """ Get the range of values that can be represented by this variable. :return: A tuple containing the minimum and maximum values. :rtype: tuple[int, int] """ return (-np.finfo(self.value).max, np.finfo(self.value).max) def _z3_(self) -> Real: """ Get the Z3 representation of the variable. :return: The Z3 FP representation of the variable. :rtype: FP """ self.add_constraint( "c_range_", lambda x: And(x >= self._range_()[0], x <= self._range_()[1]), hard=True, ) return Real(f"{self._idx_}") def _random_value_(self, bounds: tuple[float, float] = None) -> float: """ Randomize the value of the variable. :param bounds: Optional bounds for the random value. :type bounds: tuple[float, float], optional :return: A random float value within the specified bounds or the maximum value. :rtype: float """ if bounds is None: bounds = self._range_() x = np.random.uniform(min(bounds), max(bounds)) return self._cast_(x)
[docs] def to_bits(self) -> int: """ Get the raw representation of the variable. :return: The raw value. :rtype: float """ return int(self.value.view(type(self._bits_)))
[docs] def from_bits(self, raw: int) -> None: """ Convert the raw representation back to a float. :param raw: The raw value. :type raw: int """ self.value = self._cast_(type(self._bits_)(int(raw)).view(type(self.value)))
# Bitwise def __and__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __or__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __xor__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __lshift__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __rshift__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __iand__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __ior__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __ixor__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __ilshift__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __irshift__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __rand__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __ror__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __rxor__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __rlshift__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") def __rrshift__(self, other): raise NotImplementedError("Bitwise operations are not supported for floating-point variables.") # Comparison - need to override to handle NaN and other cases def __eq__(self, other): other_val = self._cast_(other) return not (np.isnan(self.value) or np.isnan(other_val)) and self.value == other_val def __ne__(self, other): other_val = self._cast_(other) return np.isnan(self.value) or np.isnan(other_val) or self.value != other_val def __lt__(self, other): other_val = self._cast_(other) return not (np.isnan(self.value) or np.isnan(other_val)) and self.value < other_val def __le__(self, other): other_val = self._cast_(other) return not (np.isnan(self.value) or np.isnan(other_val)) and self.value <= other_val def __gt__(self, other): other_val = self._cast_(other) return not (np.isnan(self.value) or np.isnan(other_val)) and self.value > other_val def __ge__(self, other): other_val = self._cast_(other) return not (np.isnan(self.value) or np.isnan(other_val)) and self.value >= other_val
[docs] class Fp32(Fp16):
[docs] def __init__(self, name: str, value: float, auto_random: bool = True, fmt: Callable[..., float] = str) -> None: """ Initialize an instance of the class. :param name: The name of the instance. :type name: str :param value: The value to be assigned to the instance. :type value: int :param auto_random: Flag to enable automatic randomization, defaults to True. :type auto_random: bool, optional :param fmt: The format to be used, defaults to hex. :type fmt: function, optional """ if not hasattr(self, "value"): self.value = np.float32(0) self._bits_ = np.uint32(0) super().__init__(name, value, auto_random=auto_random)
[docs] class Fp64(Fp16):
[docs] def __init__(self, name: str, value: float, auto_random: bool = True, fmt: Callable[..., float] = str) -> None: """ Initialize an instance of the class. :param name: The name of the instance. :type name: str :param value: The value to be assigned to the instance. :type value: int :param auto_random: Flag to enable automatic randomization, defaults to True. :type auto_random: bool, optional :param fmt: The format to be used, defaults to hex. :type fmt: function, optional """ if not hasattr(self, "value"): self.value = np.float64(0) self._bits_ = np.uint64(0) super().__init__(name, value, auto_random=auto_random)
def _range_(self) -> tuple[int, int]: """ Get the range of values that can be represented by this variable. :return: A tuple containing the minimum and maximum values. :rtype: tuple[int, int] """ return (-1e100, 1e100) # Reduced to allow randomization
Half = Fp16 Float = Fp32 Double = Fp64 __all__ = ["Fp16", "Fp32", "Fp64", "Half", "Float", "Double"]