123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- # interval.py: class implementing interval arithmetic
- # Copyright 2022 Romain Serra
- # 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.
- import math
- from typing import Union, Optional, Callable
- import numpy as np
- from swiftt.algebraic_abstract import AlgebraicAbstract
- IntervalOrScalar = Union[float, "Interval"]
- class Interval(AlgebraicAbstract):
- """Class representing intervals of real numbers.
- Attributes:
- _lb (float): lower bound.
- _ub (float): upper bound.
- """
- def __init__(self, lb: float, ub: float) -> None:
- self._lb = lb # no call to property on purpose as no sanity check is needed
- self.ub = ub
- def __len__(self) -> float:
- return self._ub - self._lb
- @property
- def ub(self) -> float:
- return self._ub
- @ub.setter
- def ub(self, ub: float) -> None:
- if ub < self.lb:
- raise ValueError("The upper bound cannot be strictly less than the lower one.")
- self._ub = ub
- @property
- def lb(self) -> float:
- return self._lb
- @lb.setter
- def lb(self, lb: float) -> None:
- if lb > self.ub:
- raise ValueError("The lower bound cannot be strictly greater than the upper one.")
- self._lb = lb
- def copy(self) -> "Interval":
- return Interval(self._lb, self._ub)
- @staticmethod
- def singleton(point: float) -> "Interval":
- return Interval(point, point)
- def __add__(self, other: IntervalOrScalar) -> "Interval":
- if isinstance(other, Interval):
- return Interval(self._lb + other.lb, self._ub + other.ub)
- # scalar case
- return Interval(self._lb + other, self._ub + other)
- def contains(self, other: IntervalOrScalar) -> bool:
- if isinstance(other, Interval):
- return other.lb >= self._lb and other.ub <= self._ub
- # scalar case
- return self._ub >= other >= self._lb
- def contains_zero(self) -> bool:
- return self.contains(0.)
- def __neg__(self) -> "Interval":
- return Interval(-self._ub, -self._lb)
- def __sub__(self, other: IntervalOrScalar) -> "Interval":
- if isinstance(other, Interval):
- return self + other.__neg__()
- # scalar case
- return Interval(self._lb - other, self._ub - other)
- def __mul__(self, other: IntervalOrScalar) -> "Interval":
- if isinstance(other, Interval):
- candidates = np.array([self._lb * other.lb, self._ub * other.lb, self._ub * other.ub,
- self._lb * other.ub])
- return Interval(np.min(candidates), np.max(candidates))
- # scalar case
- return self * Interval.singleton(other)
- def reciprocal(self) -> "Interval":
- if self.contains_zero():
- return Interval(-np.inf, np.inf)
- if self._ub != 0. and self._lb != 0.:
- inter = np.sort(1. / np.array([self._lb, self._ub]))
- return Interval(inter[0], inter[1])
- if self._ub != 0.:
- return Interval(1. / self._ub, np.inf)
- return Interval(-np.inf, 1. / self._lb)
- def __pow__(self, power: Union[int, float], modulo: Optional[float] = None) -> "Interval":
- if isinstance(power, int):
- if int(power / 2) == power / 2.:
- if self._lb >= 0:
- return Interval(self._lb**power, self._ub**power)
- if self._ub < 0.:
- return Interval(self._ub**power, self._lb**power)
- return Interval(0., max(self._lb**power, self._ub**power))
- return Interval(self._lb**power, self._ub**power)
- raise NotImplementedError
- def __str__(self) -> str:
- return "[" + str(self._lb) + ", " + str(self._ub) + "]"
- def __eq__(self, other: IntervalOrScalar) -> bool:
- if isinstance(other, Interval):
- return self._ub == other.ub and self._lb == other.lb
- # scalar case
- return self == self.singleton(other)
- def __abs__(self) -> "Interval":
- fabs = np.fabs([self._lb, self._ub])
- if self.contains_zero():
- return Interval(0., np.max(fabs))
- return Interval(np.min(fabs), np.max(fabs))
- def increasing_intrinsic(self, func: Callable) -> "Interval":
- return Interval(func(self.lb), func(self.ub))
- def sqrt(self) -> "Interval":
- return self.increasing_intrinsic(math.sqrt)
- def exp(self) -> "Interval":
- return self.increasing_intrinsic(math.exp)
- def log(self) -> "Interval":
- return self.increasing_intrinsic(math.log)
|