diff --git a/mbcp/mp_math/const.py b/mbcp/mp_math/const.py new file mode 100644 index 0000000..69f0ae7 --- /dev/null +++ b/mbcp/mp_math/const.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/25 下午9:45 +@Author : snowykami +@Email : snowykami@outlook.com +@File : const.py +@Software: PyCharm +""" +import math + + +PI = math.pi +E = math.e +GOLDEN_RATIO = (1 + math.sqrt(5)) / 2 +GAMMA = 0.57721566490153286060651209008240243104215933593992 +EPSILON = 1e-8 + diff --git a/mbcp/mp_math/equation.py b/mbcp/mp_math/equation.py index e6a2a03..85ab9aa 100644 --- a/mbcp/mp_math/equation.py +++ b/mbcp/mp_math/equation.py @@ -8,13 +8,14 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @File : equation.py @Software: PyCharm """ -import numpy as np -from .point import Point3 -from .mp_math_typing import ONE_VARIABLE_FUNC, TWO_VARIABLES_FUNC, THREE_VARIABLES_FUNC +from mbcp.mp_math.mp_math_typing import OneVarFunc, Var, MultiVarFunc, Number +from mbcp.mp_math.point import Point3 +from mbcp.mp_math.const import EPSILON + class CurveEquation: - def __init__(self, x_func: ONE_VARIABLE_FUNC, y_func: ONE_VARIABLE_FUNC, z_func: ONE_VARIABLE_FUNC): + def __init__(self, x_func: OneVarFunc, y_func: OneVarFunc, z_func: OneVarFunc): """ 曲线方程。 :param x_func: @@ -25,14 +26,45 @@ class CurveEquation: self.y_func = y_func self.z_func = z_func - def __call__(self, *t: float) -> "Point3" | tuple["Point3"]: + def __call__(self, *t: Var) -> Point3 | tuple[Point3, ...]: + """ + 计算曲线上的点。 + Args: + *t: + + Returns: + + """ if len(t) == 1: return Point3(self.x_func(t[0]), self.y_func(t[0]), self.z_func(t[0])) else: - # np加速 - ... - - + return tuple([Point3(x, y, z) for x, y, z in zip(self.x_func(t), self.y_func(t), self.z_func(t))]) def __str__(self): return "CurveEquation()" + + +def get_partial_derivative_func(func: MultiVarFunc, var: int | tuple[int, ...], epsilon: Number = EPSILON) -> MultiVarFunc: + """ + 求N元函数偏导函数。 + Args: + func: 函数 + var: 变量位置,可为整数(一阶偏导)或整数元组(高阶偏导) + epsilon: 偏移量 + Returns: + 偏导函数 + """ + if isinstance(var, int): + def partial_derivative_func(*args: Var) -> Var: + args_list_plus = list(args) + args_list_plus[var] += epsilon + args_list_minus = list(args) + args_list_minus[var] -= epsilon + return (func(*args_list_plus) - func(*args_list_minus)) / (2 * epsilon) + return partial_derivative_func + elif isinstance(var, tuple): + for i in var: + func = get_partial_derivative_func(func, i, epsilon) + return func + else: + raise ValueError("Invalid var type") diff --git a/mbcp/mp_math/mp_math_typing.py b/mbcp/mp_math/mp_math_typing.py index 6ee2c73..1acc84d 100644 --- a/mbcp/mp_math/mp_math_typing.py +++ b/mbcp/mp_math/mp_math_typing.py @@ -8,11 +8,26 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @File : mp_math_typing.py @Software: PyCharm """ -from typing import Callable, Iterable, TypeAlias +from typing import Callable, Iterable, TypeAlias, TypeVar -"""自变量""" -VAR: TypeAlias = float | Iterable[float] # 为后期支持多维矢量化做准备 +RealNumber: TypeAlias = int | float +Number: TypeAlias = RealNumber | complex +SingleVar = TypeVar("SingleVar", bound=Number) +ArrayVar = TypeVar("ArrayVar", bound=Iterable[Number]) +Var: TypeAlias = SingleVar | ArrayVar -ONE_VARIABLE_FUNC: TypeAlias = Callable[[VAR], float] -TWO_VARIABLES_FUNC: TypeAlias = Callable[[VAR, VAR], float] -THREE_VARIABLES_FUNC: TypeAlias = Callable[[VAR, VAR, VAR], float] +OneSingleVarFunc: TypeAlias = Callable[[SingleVar], SingleVar] +OneArrayFunc: TypeAlias = Callable[[ArrayVar], ArrayVar] +OneVarFunc: TypeAlias = OneSingleVarFunc | OneArrayFunc + +TwoSingleVarFunc: TypeAlias = Callable[[SingleVar, SingleVar], SingleVar] +TwoArrayFunc: TypeAlias = Callable[[ArrayVar, ArrayVar], ArrayVar] +TwoVarFunc: TypeAlias = TwoSingleVarFunc | TwoArrayFunc + +ThreeSingleVarFunc: TypeAlias = Callable[[SingleVar, SingleVar, SingleVar], SingleVar] +ThreeArrayFunc: TypeAlias = Callable[[ArrayVar, ArrayVar, ArrayVar], ArrayVar] +ThreeVarFunc: TypeAlias = ThreeSingleVarFunc | ThreeArrayFunc + +MultiSingleVarFunc: TypeAlias = Callable[..., SingleVar] +MultiArrayFunc: TypeAlias = Callable[..., ArrayVar] +MultiVarFunc: TypeAlias = MultiSingleVarFunc | MultiArrayFunc diff --git a/mbcp/mp_math/utils.py b/mbcp/mp_math/utils.py index 4a6b795..368a302 100644 --- a/mbcp/mp_math/utils.py +++ b/mbcp/mp_math/utils.py @@ -8,6 +8,9 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @File : utils.py @Software: PyCharm """ +from typing import overload + +from mbcp.mp_math.mp_math_typing import RealNumber def clamp(x: float, min_: float, max_: float) -> float: @@ -22,3 +25,34 @@ def clamp(x: float, min_: float, max_: float) -> float: 限制后的值 """ return max(min(x, max_), min_) + + +class Approx(float): + """ + 用于近似比较浮点数的类。 + """ + epsilon = 0.001 + """全局近似值。""" + + def __new__(cls, x: RealNumber): + return super().__new__(cls, x) + + def __eq__(self, other): + return abs(self - other) < Approx.epsilon + + def __ne__(self, other): + return not self.__eq__(other) + + +def approx(x: float, y: float = 0.0, epsilon: float = 0.0001) -> bool: + """ + 判断两个数是否近似相等。或包装一个实数,用于判断是否近似于0。 + Args: + x: + y: + epsilon: + + Returns: + 是否近似相等 + """ + return abs(x - y) < epsilon diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 0000000..7cea251 --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +log_cli = true +log_level = INFO \ No newline at end of file diff --git a/tests/test_partial_derivative.py b/tests/test_partial_derivative.py new file mode 100644 index 0000000..6afe85e --- /dev/null +++ b/tests/test_partial_derivative.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +""" +偏导测试 + +""" +import logging + +from mbcp.mp_math.mp_math_typing import RealNumber + + +def three_var_func(x: RealNumber, y: RealNumber) -> RealNumber: + return x ** 3 * y ** 2 - 3 * x * y ** 3 - x * y + 1 + + +class TestPartialDerivative: + # 样例来源:同济大学《高等数学》第八版下册 第九章第二节 例6 + def test_2v_1o_1v(self): + """测试二元函数关于第一个变量(x)的一阶偏导 df/dx""" + + from mbcp.mp_math.utils import Approx + from mbcp.mp_math.equation import get_partial_derivative_func + + partial_derivative_func = get_partial_derivative_func(three_var_func, (0,)) + + # assert partial_derivative_func(1, 2, 3) == 4.0 + def df_dx(x, y): + """原函数关于x的偏导""" + return 3 * (x ** 2) * (y ** 2) - 3 * (y ** 3) - y + logging.info(f"Expected: {df_dx(1, 2)}, Actual: {partial_derivative_func(1, 2)}") + assert Approx(partial_derivative_func(1, 2)) == df_dx(1, 2) + + def test_2v_1o_2v(self): + """测试二元函数关于第二个变量(y)的一阶偏导 df/dy""" + + from mbcp.mp_math.utils import Approx + from mbcp.mp_math.equation import get_partial_derivative_func + + partial_derivative_func = get_partial_derivative_func(three_var_func, 1) + + def df_dy(x, y): + """原函数关于y的偏导""" + return 2 * (x ** 3) * y - 9 * x * (y ** 2) - x + logging.info(f"Expected: {df_dy(1, 2)}, Actual: {partial_derivative_func(1, 2)}") + assert Approx(partial_derivative_func(1, 2)) == df_dy(1, 2) + + def test_2v_2o_12v(self): + """高阶偏导d^2f/(dxdy)""" + + from mbcp.mp_math.utils import Approx + from mbcp.mp_math.equation import get_partial_derivative_func + + partial_derivative_func = get_partial_derivative_func(three_var_func, (0, 1)) + + def df_dxdy(x, y): + """原函数关于y和x的偏导""" + return 6 * x ** 2 * y - 9 * y ** 2 - 1 + logging.info(f"Expected: {df_dxdy(1, 2)}, Actual: {partial_derivative_func(1, 2)}") + assert Approx(partial_derivative_func(1, 2)) == df_dxdy(1, 2) + + def test_2v_2o_1v2(self): + """二阶偏导d^2f/(dx^2)""" + + from mbcp.mp_math.utils import Approx + from mbcp.mp_math.equation import get_partial_derivative_func + + partial_derivative_func = get_partial_derivative_func(three_var_func, (0 , 0)) + + def df_dydx(x, y): + """原函数关于x和y的偏导""" + return 6 * x * y ** 2 + logging.info(f"Expected: {df_dydx(1, 2)}, Actual: {partial_derivative_func(1, 2)}") + assert Approx(partial_derivative_func(1, 2)) == df_dydx(1, 2) + + def test_2v_3o_1v3(self): + """高阶偏导d^3f/(dx^3)""" + + from mbcp.mp_math.utils import Approx + from mbcp.mp_math.equation import get_partial_derivative_func + + partial_derivative_func = get_partial_derivative_func(three_var_func, (0, 0, 0)) + + def d3f_dx3(x, y): + """原函数关于x的三阶偏导""" + return 6 * (y ** 2) + logging.info(f"Expected: {d3f_dx3(1, 2)}, Actual: {partial_derivative_func(1, 2)}") + assert Approx(partial_derivative_func(1, 2)) == d3f_dx3(1, 2)