"""
Mathematical functions.

This module provides comprehensive mathematical functions including:
- Basic operations
- Power and logarithmic functions
- Trigonometric functions
- Hyperbolic functions
- Comparison functions
- Float checks
"""
from __future__ import annotations

from . import NumberValue, FloatValue, _get_number_value
from ..frontend.base import Library, Expression, Field
from ..frontend.core import Numeric, Number, Integer, Float, make_overloads, minus, mul, div, power
from relationalai.util.docutils import include_in_docs

__include_in_docs__ = True

# the front-end library object
library = Library("math")

# ------------------------------
# Basics
# ------------------------------
_abs = library.Relation("abs", [Field.input("value", Numeric), Field("result", Numeric)], overloads=make_overloads([Number, Float], 2))

@include_in_docs
def abs(value: NumberValue) -> Expression:
    """
    Compute the absolute value.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing the absolute value of the input. Returns `Number` if the
        input is `Number`, or `Float` if the input is `Float`.

    Examples
    --------
    Compute absolute value:

    >>> select(math.abs(Transaction.amount))
    >>> select(math.abs(Temperature.deviation + 1.0))
    """
    return _abs(value)

_ceil = library.Relation("ceil", [Field.input("value", Numeric), Field("result", Numeric)], overloads=make_overloads([Number, Float], 2))

@include_in_docs
def ceil(value: NumberValue) -> Expression:
    """
    Round up to the nearest integer.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing the ceiling value of the input. Returns `Number` if the
        input is `Number`, or `Float` if the input is `Float`.

    Examples
    --------
    Round up to nearest integer:

    >>> select(math.ceil(Product.price))
    >>> select(math.ceil(3.14))
    """
    return _ceil(value)

_clip = library.Relation("clip", [Field.input("value", Numeric), Field.input("lower", Numeric), Field.input("upper", Numeric), Field("result", Numeric)], overloads=make_overloads([Number, Float], 4))

@include_in_docs
def clip(value: NumberValue, lower: NumberValue, upper: NumberValue) -> Expression:
    """
    Clip (clamp) a value to a range.

    Parameters
    ----------
    value: NumberValue
        The value to clip.
    lower: NumberValue
        The lower bound.
    upper: NumberValue
        The upper bound.

    Returns
    -------
    Expression
        An `Expression` computing the clipped value (min(upper, max(lower, value))). Returns
        `Number` if the input is `Number`, or `Float` if the input is `Float`.

    Examples
    --------
    Clip values to a range:

    >>> select(math.clip(Employee.score, 0, 100))
    >>> select(math.clip(Temperature.reading, -10, 40))
    """
    # CLIP = CLAMP(v,min,max) = LEAST(max,GREATEST(min,v))
    return _clip(lower, upper, value)

_factorial = library.Relation("factorial", [Field.input("value", Integer), Field("result", Integer)])

@include_in_docs
def factorial(value: NumberValue) -> Expression:
    """
    Compute the factorial of a non-negative integer.

    Parameters
    ----------
    value: NumberValue
        The non-negative integer input.

    Returns
    -------
    Expression
        An `Expression` computing the factorial. Returns `Integer`. If the input value is
        not statically known to be non-negative, but turns out to be negative at runtime,
        returns `NaN`.

    Raises
    ------
    ValueError
        If value can be computed statically and is negative.

    Examples
    --------
    Compute factorial:

    >>> select(math.factorial(5))
    >>> select(math.factorial(Calculation.n))
    """
    v = _get_number_value(value)
    if v is not None and v < 0:
        raise ValueError("Cannot take the factorial of a negative number")
    return _factorial(value)

_floor = library.Relation("floor", [Field.input("value", Numeric), Field("result", Numeric)], overloads=make_overloads([Number, Float], 2))

@include_in_docs
def floor(value: NumberValue) -> Expression:
    """
    Round down to the nearest integer.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing the floor value of the input. Returns `Number` if the
        input is `Number`, or `Float` if the input is `Float`.

    Examples
    --------
    Round down to nearest integer:

    >>> select(math.floor(Product.price))
    >>> select(math.floor(3.99))
    """
    return _floor(value)

@include_in_docs
def isclose(x: NumberValue, y: NumberValue, tolerance: NumberValue = 1e-9) -> Expression:
    """
    Check if two values are approximately equal within an absolute tolerance.

    Uses ``abs(x - y) <= abs(tolerance)``.  Unlike Python's :func:`math.isclose`,
    there is no relative tolerance component.

    Parameters
    ----------
    x: NumberValue
        First value.
    y: NumberValue
        Second value.
    tolerance: NumberValue
        Absolute tolerance for comparison. Default: 1e-9.

    Returns
    -------
    Expression
        An `Expression` that evaluates to true if abs(x - y) <= abs(tolerance).

    Examples
    --------
    Check if a value is close to a target:

    >>> select(math.isclose(Product.price, 9.99, 0.01))
    """
    # Expression composition (not library.Relation) because the underlying
    # stdlib function is @inline and library.Relation cannot resolve those.
    return abs(x - y) <= abs(tolerance)  # type: ignore[operator]

_sign = library.Relation("sign", [Field.input("x", Numeric), Field("result", Integer)], overloads=[[Number, Integer], [Float, Integer]])

@include_in_docs
def sign(x: NumberValue) -> Expression:
    """
    Compute the sign of a number.

    Parameters
    ----------
    x: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing -1 for negative, 0 for zero, 1 for positive. Returns `Integer`.

    Examples
    --------
    Get sign of values:

    >>> select(math.sign(Account.balance))
    >>> select(math.sign(Temperature.deviation))
    """
    return _sign(x)

_trunc_divide = library.Relation("trunc_divide", [Field.input("numerator", Number), Field.input("denominator", Number), Field("result", Number)])

@include_in_docs
def trunc_divide(numerator: NumberValue, denominator: NumberValue) -> Expression:
    """
    Perform truncated division.

    Parameters
    ----------
    numerator: NumberValue
        The numerator.
    denominator: NumberValue
        The denominator.

    Returns
    -------
    Expression
        An `Expression` computing the truncated quotient. Returns `Number`.
    """
    return _trunc_divide(numerator, denominator)

_maximum = library.Relation("maximum", [Field.input("left", Numeric), Field.input("right", Numeric), Field("result", Numeric)], overloads=make_overloads([Number, Float], 3))

@include_in_docs
def maximum(left: NumberValue, right: NumberValue) -> Expression:
    """
    Find the maximum of two values.

    Parameters
    ----------
    left: NumberValue
        First value.
    right: NumberValue
        Second value.

    Returns
    -------
    Expression
        An `Expression` computing the maximum value. Returns `Number` if the inputs are
        `Number`, or `Float` if the inputs are `Float`.

    Examples
    --------
    Find maximum of two values:

    >>> select(math.maximum(Employee.salary, Employee.bonus))
    >>> select(math.maximum(10, Product.discount))
    """
    # GREATEST
    return _maximum(left, right)

_minimum = library.Relation("minimum", [Field.input("left", Numeric), Field.input("right", Numeric), Field("result", Numeric)], overloads=make_overloads([Number, Float], 3))

@include_in_docs
def minimum(left: NumberValue, right: NumberValue) -> Expression:
    """
    Find the minimum of two values.

    Parameters
    ----------
    left: NumberValue
        First value.
    right: NumberValue
        Second value.

    Returns
    -------
    Expression
        An `Expression` computing the minimum value. Returns `Number` if the inputs are
        `Number`, or `Float` if the inputs are `Float`.

    Examples
    --------
    Find minimum of two values:

    >>> select(math.minimum(Product.list_price, Product.sale_price))
    >>> select(math.minimum(100, Order.total))
    """
    # LEAST
    return _minimum(left, right)

_isinf = library.Relation("isinf", [Field.input("value", Float)])

@include_in_docs
def isinf(value: FloatValue) -> Expression:
    """
    Check if a float value is infinite.

    Parameters
    ----------
    value: FloatValue
        The float value to check.

    Returns
    -------
    Expression
        An `Expression` that evaluates to true if value is infinite.
    """
    return _isinf(value)

_isnan = library.Relation("isnan", [Field.input("value", Float)])

@include_in_docs
def isnan(value: FloatValue) -> Expression:
    """
    Check if a float value is NaN (Not a Number).

    Parameters
    ----------
    value: FloatValue
        The float value to check.

    Returns
    -------
    Expression
        An `Expression` that evaluates to true if value is NaN.
    """
    return _isnan(value)

# -------------------------------
# Power and Logarithmic Functions
# -------------------------------

_cbrt = library.Relation("cbrt", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def cbrt(value: NumberValue) -> Expression:
    """
    Compute the cube root.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing the cube root. Returns `Float`.
    """
    return _cbrt(value)

_sqrt = library.Relation("sqrt", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def sqrt(value: NumberValue) -> Expression:
    """
    Compute the square root.

    Parameters
    ----------
    value: NumberValue
        The non-negative input value.

    Returns
    -------
    Expression
        An `Expression` computing the square root. Returns `Float`. If the input value is
        not statically known to be non-negative, but turns out to be negative at runtime,
        returns `NaN`.

    Raises
    ------
    ValueError
        If value is negative.

    Examples
    --------
    Compute square root:

    >>> select(math.sqrt(16))
    >>> select(math.sqrt(Geometry.area))
    """
    v = _get_number_value(value)
    if v is not None and v < 0:
        raise ValueError("Cannot take the square root of a negative number")
    return _sqrt(value)

_exp = library.Relation("exp", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def exp(value: NumberValue) -> Expression:
    """
    Compute the exponential function (e^value).

    Parameters
    ----------
    value: NumberValue
        The exponent.

    Returns
    -------
    Expression
        An `Expression` computing e raised to the power of value. Returns `Float`.

    Examples
    --------
    Compute exponential:

    >>> select(math.exp(1))
    >>> select(math.exp(Growth.rate))
    """
    return _exp(value)

_power = library.Relation("power", [Field.input("base", Numeric), Field.input("exponent", Numeric), Field("result", Float)],
                          overloads=[
                              #[Number, Number, Float], # v0 and engines are not handling this correctly
                              [Float, Float, Float]])

@include_in_docs
def pow(base: NumberValue, exponent: NumberValue) -> Expression:
    """
    Raise a base to an exponent.

    Parameters
    ----------
    base: NumberValue
        The base value.
    exponent: NumberValue
        The exponent.

    Returns
    -------
    Expression
        An `Expression` computing `base^exponent`. Returns `Float`.

    Examples
    --------
    Raise to a power:

    >>> select(math.pow(2, 8))
    >>> select(math.pow(Investment.principal, Investment.years))
    """
    return _power(base, exponent)

_natural_log = library.Relation("natural_log", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def natural_log(value: NumberValue) -> Expression:
    """
    Compute the natural logarithm (ln).

    Parameters
    ----------
    value: NumberValue
        A positive input number value. If the input value is not statically known to be
        non-positive, but turns out to be non-positive at runtime, returns `NaN`.

    Returns
    -------
    Expression
        An `Expression` computing ln(value). Always returns a `Float`.

    Raises
    ------
    ValueError
        If `value` is can be computed statically and is non-positive.
    """
    v = _get_number_value(value)
    if v is not None and v <= 0:
        raise ValueError("Cannot take the logarithm of a non-positive number")
    return _natural_log(value)

_log = library.Relation("log", [Field.input("base", Numeric), Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Number, Float], [Float, Float, Float]])

@include_in_docs
def log(x: NumberValue, base: NumberValue | None = None) -> Expression:
    """
    Compute the logarithm with optional base.

    Parameters
    ----------
    x: NumberValue
        The positive input value. If the input value is not statically known to be
        non-positive, but turns out to be non-positive at runtime, returns `NaN`.
    base: NumberValue | None
        The logarithm base. If None, computes natural log. Default: None.

    Returns
    -------
    Expression
        An `Expression` computing log_base(x), or ln(x) if base is None. Returns `Float`.

    Raises
    ------
    ValueError
        If `x` can be computed statically and is non-positive.

    Examples
    --------
    Compute logarithm:

    >>> select(math.log(100, 10))
    >>> select(math.log(Signal.amplitude))
    """
    v = _get_number_value(x)
    if v is not None and v <= 0:
        raise ValueError("Cannot take the logarithm of a non-positive number")
    if base is None:
        return natural_log(x)
    return _log(base, x)

@include_in_docs
def log2(value: NumberValue) -> Expression:
    """
    Compute the base-2 logarithm.

    Parameters
    ----------
    value: NumberValue
        The positive input value. If the input value is not statically known to be
        non-positive, but turns out to be non-positive at runtime, returns `NaN`.

    Returns
    -------
    Expression
        An `Expression` computing log_2(value). Returns `Float`.

    Raises
    ------
    ValueError
        If `x` can be computed statically and is non-positive.

    Examples
    --------
    Compute base-2 logarithm:

    >>> select(math.log2(1024))
    >>> select(math.log2(Data.size))
    """
    return log(value, 2)

@include_in_docs
def log10(value: NumberValue) -> Expression:
    """
    Compute the base-10 logarithm.

    Parameters
    ----------
    value: NumberValue
        The positive input value. If the input value is not statically known to be
        non-positive, but turns out to be non-positive at runtime, returns `NaN`.

    Returns
    -------
    Expression
        An `Expression` computing log_10(value). Returns `Float`.

    Raises
    ------
    ValueError
        If `x` can be computed statically and is non-positive.

    Examples
    --------
    Compute base-10 logarithm:

    >>> select(math.log10(1000))
    >>> select(math.log10(Signal.power))
    """
    return log(value, 10)



# ------------------------------
# Trigonometry
# ------------------------------

_degrees = library.Relation("degrees", [Field.input("radians", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def degrees(radians: NumberValue) -> Expression:
    """
    Convert radians to degrees.

    Parameters
    ----------
    radians: NumberValue
        The angle in radians.

    Returns
    -------
    Expression
        An `Expression` computing the angle in degrees. Returns `Float`.

    Examples
    --------
    Convert radians to degrees:

    >>> select(math.degrees(3.14159))
    >>> select(math.degrees(Location.lat_rad))
    """
    return _degrees(radians)

_radians = library.Relation("radians", [Field.input("degrees", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def radians(degrees: NumberValue) -> Expression:
    """
    Convert degrees to radians.

    Parameters
    ----------
    degrees: NumberValue
        The angle in degrees.

    Returns
    -------
    Expression
        An `Expression` computing the angle in radians. Returns `Float`.

    Examples
    --------
    Convert degrees to radians:

    >>> select(math.radians(180))
    >>> select(math.radians(Location.lat_deg))
    """
    return _radians(degrees)

_cos = library.Relation("cos", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def cos(value: NumberValue) -> Expression:
    """
    Compute the cosine.

    Parameters
    ----------
    value: NumberValue
        The angle in radians.

    Returns
    -------
    Expression
        An `Expression` computing cos(value). Returns `Float`.

    Examples
    --------
    Compute cosine:

    >>> select(math.cos(0))
    >>> select(math.cos(Wave.angle))
    """
    return _cos(value)

_sin = library.Relation("sin", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def sin(value: NumberValue) -> Expression:
    """
    Compute the sine.

    Parameters
    ----------
    value: NumberValue
        The angle in radians.

    Returns
    -------
    Expression
        An `Expression` computing sin(value). Returns `Float`.

    Examples
    --------
    Compute sine:

    >>> select(math.sin(0))
    >>> select(math.sin(Wave.phase))
    """
    return _sin(value)

_tan = library.Relation("tan", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def tan(value: NumberValue) -> Expression:
    """
    Compute the tangent.

    Parameters
    ----------
    value: NumberValue
        The angle in radians.

    Returns
    -------
    Expression
        An `Expression` computing tan(value). Returns `Float`.

    Examples
    --------
    Compute tangent:

    >>> select(math.tan(0.785))
    >>> select(math.tan(Angle.radians))
    """
    return _tan(value)

_cot = library.Relation("cot", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def cot(value: NumberValue) -> Expression:
    """
    Compute the cotangent.

    Parameters
    ----------
    value: NumberValue
        The angle in radians.

    Returns
    -------
    Expression
        An `Expression` computing cot(value). Returns `Float`.
    """
    return _cot(value)

_acos = library.Relation("acos", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def acos(value: NumberValue) -> Expression:
    """
    Compute the arccosine (inverse cosine).

    Parameters
    ----------
    value: NumberValue
        The input value (must be in [-1, 1]).

    Returns
    -------
    Expression
        An `Expression` computing acos(value) in radians. Returns `Float`.
    """
    return _acos(value)

_asin = library.Relation("asin", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def asin(value: NumberValue) -> Expression:
    """
    Compute the arcsine (inverse sine).

    Parameters
    ----------
    value: NumberValue
        The input value (must be in [-1, 1]).

    Returns
    -------
    Expression
        An `Expression` computing asin(value) in radians. Returns `Float`.
    """
    return _asin(value)

_atan = library.Relation("atan", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def atan(value: NumberValue) -> Expression:
    """
    Compute the arctangent (inverse tangent).

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing atan(value) in radians. Returns `Float`.
    """
    return _atan(value)

_acot = library.Relation("acot", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def acot(value: NumberValue) -> Expression:
    """
    Compute the arccotangent (inverse cotangent).

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing acot(value) in radians. Returns `Float`.
    """
    return _acot(value)

_cosh = library.Relation("cosh", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def cosh(value: NumberValue) -> Expression:
    """
    Compute the hyperbolic cosine.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing cosh(value). Returns `Float`.
    """
    return _cosh(value)

_sinh = library.Relation("sinh", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def sinh(value: NumberValue) -> Expression:
    """
    Compute the hyperbolic sine.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing sinh(value). Returns `Float`.
    """
    return _sinh(value)

_tanh = library.Relation("tanh", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def tanh(value: NumberValue) -> Expression:
    """
    Compute the hyperbolic tangent.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing tanh(value). Returns `Float`.
    """
    return _tanh(value)

_acosh = library.Relation("acosh", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def acosh(value: NumberValue) -> Expression:
    """
    Compute the inverse hyperbolic cosine.

    Parameters
    ----------
    value: NumberValue
        The input value (must be >= 1).

    Returns
    -------
    Expression
        An `Expression` computing acosh(value). Returns `Float`.

    Raises
    ------
    ValueError
        If value is less than 1.
    """
    v = _get_number_value(value)
    if v is not None and v < 1:
        raise ValueError("acosh expects a value greater than or equal to 1.")
    return _acosh(value)

_asinh = library.Relation("asinh", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def asinh(value: NumberValue) -> Expression:
    """
    Compute the inverse hyperbolic sine.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing asinh(value). Returns `Float`.
    """
    return _asinh(value)

_atanh = library.Relation("atanh", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def atanh(value: NumberValue) -> Expression:
    """
    Compute the inverse hyperbolic tangent.

    Parameters
    ----------
    value: NumberValue
        The input value (must be in (-1, 1) exclusive).

    Returns
    -------
    Expression
        An `Expression` computing atanh(value). Returns `Float`.

    Raises
    ------
    ValueError
        If value is not in the range (-1, 1).
    """
    v = _get_number_value(value)
    if v is not None and (v <= -1 or v >= 1):
        raise ValueError("atanh expects a value between -1 and 1, exclusive.")
    return _atanh(value)

# ------------------------------
# Special Functions
# ------------------------------
_erf = library.Relation("erf", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def erf(value: NumberValue) -> Expression:
    """
    Compute the error function.

    Parameters
    ----------
    value: NumberValue
        The input value.

    Returns
    -------
    Expression
        An `Expression` computing erf(value). Returns `Float`.
    """
    return _erf(value)

_erfinv = library.Relation("erfinv", [Field.input("value", Numeric), Field("result", Float)], overloads=[[Number, Float], [Float, Float]])

@include_in_docs
def erfinv(value: NumberValue) -> Expression:
    """
    Compute the inverse error function.

    Parameters
    ----------
    value: NumberValue
        The input value (must be in [-1, 1]).

    Returns
    -------
    Expression
        An `Expression` computing erfinv(value). Returns `Float`.

    Raises
    ------
    ValueError
        If value is not in the range [-1, 1].
    """
    v = _get_number_value(value)
    if v is not None and (v < -1 or v > 1):
        raise ValueError("erfinv expects a value between -1 and 1, inclusive.")
    return _erfinv(value)

@include_in_docs
def haversine(lat1: NumberValue, lon1: NumberValue, lat2: NumberValue, lon2: NumberValue, radius: NumberValue = 1.0) -> Expression:
    """
    Compute the haversine distance between two points on a sphere.

    The haversine formula determines the great-circle distance between two points
    on a sphere given their longitudes and latitudes.

    Parameters
    ----------
    lat1: NumberValue
        Latitude of the first point in radians.
    lon1: NumberValue
        Longitude of the first point in radians.
    lat2: NumberValue
        Latitude of the second point in radians.
    lon2: NumberValue
        Longitude of the second point in radians.
    radius: NumberValue
        Radius of the sphere. Default: 1.0.

    Returns
    -------
    Expression
        An `Expression` computing the haversine distance. Returns `Float`.

    Examples
    --------
    Calculate distance between two points on Earth (radius ~6371 km):

    >>> math.haversine(lat1, lon1, lat2, lon2, radius=6371)
    """
    # 2 * r * asin[sqrt[sin[(lat2 - lat1)/2] ^ 2 + cos[lat1] * cos[lat2] * sin[(lon2 - lon1) / 2] ^ 2]]
    return mul(2, radius, _asin(_sqrt(
        power(_sin(div(minus(lat2, lat1), 2)), 2) +
        mul(_cos(lat1), _cos(lat2), power(_sin(div(minus(lon2, lon1), 2)), 2))
    )))
