Source code for nvalchemiops.math.math

# SPDX-FileCopyrightText: Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# 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 typing import Any

import warp as wp


@wp.func
def wp_safe_divide(x: wp.float64, y: wp.float64) -> wp.float64:
    """Safe division.

    Divides x by y, with a safe division to avoid division by zero.
    """
    return wp.where(y < wp.float64(1e-8), wp.float64(0.0), x / y)


@wp.func
def wp_exp_kernel(x: wp.float64, factor: wp.float64) -> wp.float64:
    """
    Safe exponential multiplication and division.

    Calculates exp(-x * factor) / x, with a safe division to avoid division by zero.
    """
    return wp_safe_divide(wp.exp(-x * factor), x)


@wp.func
def wp_logistic(x: Any) -> Any:
    """Logistic function, evaluated so the exponent is never positive.

    The naive ``1 / (1 + exp(-x))`` overflows for large negative ``x``. Harmless in float64,
    a real failure in float32, so each branch exponentiates ``-|x|`` instead.

    Parameters
    ----------
    x : Any
        Input value

    Returns
    -------
    Any
        ``1 / (1 + exp(-x))``
    """
    one = type(x)(1.0)
    decay = wp.exp(-wp.abs(x))
    if x >= type(x)(0.0):
        return one / (one + decay)
    return decay / (one + decay)


[docs] @wp.func def wpdivmod(a: int, b: int): # type: ignore """Integer quotient and remainder. Computes ``quotient = int(a / b)`` with truncating division toward zero, then adjusts a negative C-style remainder. For the positive-divisor contract used by the neighbor kernels, this matches :func:`divmod`, including for negative dividends (for example, ``wpdivmod(-5, 3)`` and Python ``divmod(-5, 3)`` both return ``(-2, 1)``). Parameters ---------- a : int Dividend. b : int Positive divisor. Returns ------- quotient : int Floor quotient for the positive divisor. remainder : int Non-negative remainder in ``[0, |b|)`` produced by the adjustment above. """ div = int(a / b) mod = a % b if mod < 0: div -= 1 mod = b + mod return div, mod
[docs] @wp.func def wp_erfc_f32_fast(x: Any) -> Any: """Complementary error function approximation for float32. Uses the Abramowitz and Stegun approximation with maximum error ~1.5e-7. erfc(x) = 1 - erf(x) for x >= 0, and erfc(-x) = 2 - erfc(x) for x < 0. This is a float32-grade approximation: its own error (~1.5e-7) is close to float32 epsilon, so evaluating it in float64 buys no accuracy, only cost. Do not use it for float64 inputs -- use native ``wp.erfc`` there, or call :func:`wp_erfc_input_precision` to dispatch automatically. Parameters ---------- x : Any Input value Returns ------- Any erfc(x) approximation """ abs_x = wp.abs(x) # Abramowitz and Stegun constants for erfc approximation p = type(x)(0.3275911) a1 = type(x)(0.254829592) a2 = type(x)(-0.284496736) a3 = type(x)(1.421413741) a4 = type(x)(-1.453152027) a5 = type(x)(1.061405429) # Compute approximation for |x| t = type(x)(1.0) / (type(x)(1.0) + p * abs_x) t2 = t * t t3 = t2 * t t4 = t3 * t t5 = t4 * t # Polynomial approximation poly = a1 * t + a2 * t2 + a3 * t3 + a4 * t4 + a5 * t5 # Apply exponential factor exp_neg_x2 = wp.exp(-abs_x * abs_x) erfc_abs_x = poly * exp_neg_x2 # Handle sign: erfc(-x) = 2 - erfc(x) return wp.where(x >= type(x)(0.0), erfc_abs_x, type(x)(2.0) - erfc_abs_x)
[docs] @wp.func def wp_erfc(x: Any) -> Any: """Deprecated alias for :func:`wp_erfc_f32_fast`. .. deprecated:: Kept only for backward compatibility: existing callers that evaluate this float32-grade approximation at float64 get exactly the same (imprecise, for float64) result they always did. New code should call :func:`wp_erfc_input_precision`, which dispatches to native ``wp.erfc`` for float64 and to :func:`wp_erfc_f32_fast` otherwise. """ return wp_erfc_f32_fast(x)
@wp.func def wp_erfc_input_precision(x: wp.float64) -> wp.float64: """``erfc(x)`` at the precision of ``x``: native for float64, fast for float32. Native ``wp.erfc`` is exact to machine precision but costs more than the ~1.5e-7-accurate polynomial in :func:`wp_erfc_f32_fast`, which is already below float32 epsilon. Use this dispatcher in code that evaluates in the caller's input precision rather than a type hard-coded to one dtype. """ return wp.erfc(x)
[docs] @wp.func def wp_erfc_input_precision(x: wp.float32) -> wp.float32: # noqa: F811 """See the ``wp.float64`` overload.""" return wp_erfc_f32_fast(x)