from __future__ import annotations
import math
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .._typing import Bound
[docs]
def eval_math_bound(
l_x: Bound | None,
u_x: Bound | None,
l_y: Bound | None = None,
u_y: Bound | None = None,
operator: str | None = None,
) -> tuple[Bound | None, Bound | None]:
if operator == "+":
return eval_add_bound(l_x, u_x, l_y, u_y)
elif operator == "-":
return eval_subtract_bound(l_x, u_x, l_y, u_y)
elif operator == "*":
return eval_multiply_bound(l_x, u_x, l_y, u_y)
elif operator == "^":
# power is not supported as of now
return l_x, u_x
elif operator == "/":
return eval_div_bound(l_x, u_x, l_y, u_y)
elif operator == "abs":
return eval_abs_bound(l_x, u_x)
return None, None
[docs]
def eval_abs_bound(
l_x: Bound | None, u_x: Bound | None
) -> tuple[Bound | None, Bound | None]:
"""
:param l_x: lower bound
:param u_x: upper bound
:return: lower and upper bound of abs operation
"""
if l_x is not None and u_x is not None:
if l_x == math.inf or u_x == math.inf or l_x == -math.inf or u_x == -math.inf:
return 0, math.inf
elif l_x <= 0 and u_x <= 0:
return -u_x, -l_x
elif l_x >= 0 and u_x >= 0:
return l_x, u_x
elif l_x <= 0 <= u_x:
return 0, max(-l_x, u_x)
return None, None
[docs]
def eval_div_bound(
l_x: Bound | None, u_x: Bound | None, l_y: Bound | None, u_y: Bound | None
) -> tuple[Bound | None, Bound | None]:
"""
:param l_x: lower bound of left child
:param u_x: upper bound of left child
:param l_y: lower bound of right child
:param u_y: upper bound of right child
:return: lower and upper bound of div operation
"""
if l_x is not None and u_x is not None and l_y is not None and u_y is not None:
if (l_x == -math.inf and u_x == math.inf) or l_y <= 0 <= u_y:
# x is unbounded or 0 in y
return -math.inf, math.inf
elif l_x >= 0 and l_y >= 0:
# both x, y are positive
if u_y == math.inf:
lower = 0
else:
lower = l_x / u_y
if u_x == math.inf:
upper = math.inf
else:
upper = u_x / l_y
return lower, upper
elif u_x <= 0 and u_y <= 0:
# both x, y are negative
if l_y == -math.inf:
lower = 0
else:
lower = u_x / l_y
if l_x == -math.inf:
upper = math.inf
else:
upper = l_x / u_y
return lower, upper
elif l_x >= 0 >= u_y:
# x is positive and y is negative
if u_x == math.inf:
lower = -math.inf
else:
lower = l_x / u_y
if l_y == -math.inf:
upper = 0
else:
upper = l_x / l_y
return lower, upper
elif u_x <= 0 <= l_y:
# x is negative and y is positive
if l_x == -math.inf:
lower = -math.inf
else:
lower = l_x / l_y
if u_y == math.inf:
upper = 0
else:
upper = u_x / u_y
return lower, upper
elif l_x <= 0 <= u_x and l_y >= 0:
# 0 in x and y is positive
if l_x == -math.inf:
lower = -math.inf
else:
lower = l_x / l_y
if u_x == math.inf:
upper = math.inf
else:
upper = u_x / l_y
return lower, upper
elif l_x <= 0 <= u_x and u_y <= 0:
# 0 in x and y is negative
if u_x == math.inf:
lower = -math.inf
else:
lower = u_x / u_y
if l_x == -math.inf:
upper = math.inf
else:
upper = l_x / u_y
return lower, upper
return None, None
[docs]
def eval_multiply_bound(
l_x: Bound | None, u_x: Bound | None, l_y: Bound | None, u_y: Bound | None
) -> tuple[Bound | None, Bound | None]:
"""
:param l_x: lower bound of left child
:param u_x: upper bound of left child
:param l_y: lower bound of right child
:param u_y: upper bound of right child
:return: lower and upper bound of multiply operation
"""
if l_x is not None and u_x is not None and l_y is not None and u_y is not None:
if (l_x == -math.inf and u_x == math.inf) or (
l_y == -math.inf and u_y == math.inf
):
# one of x, y is unbounded
return -math.inf, math.inf
elif l_x >= 0 and l_y >= 0:
# both x, y are positive
if u_x == math.inf or u_y == math.inf:
return l_x * l_y, math.inf
return l_x * l_y, u_x * u_y
elif u_x <= 0 and u_y <= 0:
# both x, y are negative
if l_x == -math.inf or l_y == -math.inf:
return u_x * u_y, math.inf
return u_x * u_y, l_x * l_y
elif l_x >= 0 >= u_y:
# x is positive and y is negative
if u_x == math.inf or l_y == -math.inf:
return -math.inf, l_x * u_y
return u_x * l_y, l_x * u_y
elif u_x <= 0 <= l_y:
# x is negative and y is positive
if l_x == -math.inf or u_y == math.inf:
return -math.inf, u_x * l_y
return l_x * u_y, u_x * l_y
elif l_x <= 0 <= u_x and l_y >= 0:
# 0 in x and y is positive
if l_x == -math.inf or u_y == math.inf:
lower = -math.inf
else:
lower = l_x * u_y
if u_x == math.inf or u_y == math.inf:
upper = math.inf
else:
upper = u_x * u_y
return lower, upper
elif l_x <= 0 <= u_x and u_y <= 0:
# 0 in x and y is negative
if u_x == math.inf or l_y == -math.inf:
lower = -math.inf
else:
lower = u_x * l_y
if l_x == -math.inf or l_y == -math.inf:
upper = math.inf
else:
upper = l_x * l_y
return lower, upper
elif l_x >= 0 and l_y <= 0 <= u_y:
# 0 in y and x is positive
if u_x == math.inf or l_y == -math.inf:
lower = -math.inf
else:
lower = u_x * l_y
if u_x == math.inf or u_y == math.inf:
upper = math.inf
else:
upper = u_x * u_y
return lower, upper
elif u_x <= 0 and l_y <= 0 <= u_y:
# 0 in y and x is negative
if l_x == -math.inf or u_y == math.inf:
lower = -math.inf
else:
lower = l_x * u_y
if l_x == -math.inf or l_y == -math.inf:
upper = math.inf
else:
upper = l_x * l_y
return lower, upper
elif l_x <= 0 <= u_x and l_y <= 0 <= u_y:
# 0 in x and 0 in y
if (
l_x == -math.inf
or l_y == -math.inf
or u_x == math.inf
or u_y == math.inf
):
# unbounded
return -math.inf, math.inf
else:
return min(l_x * u_y, u_x * l_y), max(u_x * u_y, l_x * l_y)
return None, None
[docs]
def eval_subtract_bound(
l_x: Bound | None, u_x: Bound | None, l_y: Bound | None, u_y: Bound | None
) -> tuple[Bound | None, Bound | None]:
"""
:param l_x: lower bound of left child
:param u_x: upper bound of left child
:param l_y: lower bound of right child
:param u_y: upper bound of right child
:return: lower and upper bound of subtract operation
"""
if l_x is not None and u_x is not None and l_y is not None and u_y is not None:
# lower bound
if l_x == -math.inf or u_y == math.inf:
lower = -math.inf
else:
lower = l_x - u_y
# upper bound
if u_x == math.inf or l_y == -math.inf:
upper = math.inf
else:
upper = u_x - l_y
return lower, upper
return None, None
[docs]
def eval_add_bound(
l_x: Bound | None, u_x: Bound | None, l_y: Bound | None, u_y: Bound | None
) -> tuple[Bound | None, Bound | None]:
"""
:param l_x: lower bound of left child
:param u_x: upper bound of left child
:param l_y: lower bound of right child
:param u_y: upper bound of right child
:return: lower and upper bound of add operation
"""
if l_x is not None and u_x is not None and l_y is not None and u_y is not None:
# lower bound
if l_x == -math.inf or l_y == -math.inf:
lower = -math.inf
else:
lower = l_x + l_y
# upper bound
if u_x == math.inf or u_y == math.inf:
upper = math.inf
else:
upper = u_x + u_y
return lower, upper
return None, None