diff --git a/main.py b/main.py index 7622630..4e86335 100644 --- a/main.py +++ b/main.py @@ -7,4 +7,147 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @Email : snowykami@outlook.com @File : main.py @Software: PyCharm -""" \ No newline at end of file +""" +import logging + +from mbcp.mp_math.line import Line3 +from mbcp.mp_math.plane import Plane3 +from mbcp.mp_math.point import Point3 + +# def ac8s4e4(): +# """ +# 第八章第四节例4 +# 问题:求与两平面x-4z-3=0和2x-y-5z-1=0的交线平行且过点(-3, 2, 5)的直线方程。 +# """ +# correct_ans = Line3(4, 3, 1, 1) +# +# pl1 = Plane3(1, 0, -4, -3) +# pl2 = Plane3(2, -1, -5, -1) +# p = Point3(-3, 2, 5) +# """解法1""" +# # 求直线方向向量s +# s = pl1.normal @ pl2.normal +# actual_ans = Line3.from_point_and_direction(p, s) +# +# logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}") +# assert actual_ans == correct_ans +# +# """解法2""" +# # 过点p且与pl1平行的平面pl3 +# pl3 = pl1.cal_parallel_plane3(p) +# # 过点p且与pl2平行的平面pl4 +# pl4 = pl2.cal_parallel_plane3(p) +# # 求pl3和pl4的交线 +# actual_ans = pl3.cal_intersection_line3(pl4) +# print(pl3, pl4, actual_ans) +# +# logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}") +# assert actual_ans == correct_ans +# +# +# ac8s4e4() +import logging + +from mbcp.mp_math.mp_math_typing import RealNumber +from mbcp.mp_math.utils import Approx + + +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) + + def test_possible_error(self): + from mbcp.mp_math.equation import get_partial_derivative_func + def two_vars_func(x: RealNumber, y: RealNumber) -> RealNumber: + return x ** 2 * y ** 2 + + partial_func = get_partial_derivative_func(two_vars_func, 0) + partial_func_2 = get_partial_derivative_func(two_vars_func, (0, 0)) + assert Approx(partial_func_2(1, 2)) == 8 + + +TestPartialDerivative().test_2v_1o_1v() +TestPartialDerivative().test_2v_1o_2v() +TestPartialDerivative().test_2v_2o_12v() +TestPartialDerivative().test_2v_2o_1v2() +TestPartialDerivative().test_2v_3o_1v3() + +TestPartialDerivative().test_possible_error() diff --git a/mbcp/mp_math/angle.py b/mbcp/mp_math/angle.py new file mode 100644 index 0000000..5dc140f --- /dev/null +++ b/mbcp/mp_math/angle.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/26 上午6:29 +@Author : snowykami +@Email : snowykami@outlook.com +@File : angle.py +@Software: PyCharm +""" +from typing import overload + +from .const import PI + + +class AnyAngle: + def __init__(self, value: float, is_radian: bool = False): + """ + 任意角度。 + Args: + value: 角度或弧度值 + is_radian: 是否为弧度,默认为否 + """ + if is_radian: + self.radian = value + else: + self.radian = value * PI / 180 + + @property + def complementary(self) -> "AnyAngle": + """ + 余角:两角的和为90°。 + Returns: + 余角 + """ + return AnyAngle(PI / 2 - self.minimum_positive.radian, is_radian=True) + + @property + def supplementary(self) -> "AnyAngle": + """ + 补角:两角的和为180°。 + Returns: + 补角 + """ + return AnyAngle(PI - self.minimum_positive.radian, is_radian=True) + + @property + def degree(self) -> float: + """ + 角度。 + Returns: + 弧度 + """ + return self.radian * 180 / PI + + @property + def minimum_positive(self) -> "AnyAngle": + """ + 最小正角。 + Returns: + 最小正角度 + """ + return AnyAngle(self.radian % (2 * PI)) + + @property + def maximum_negative(self) -> "AnyAngle": + """ + 最大负角。 + Returns: + 最大负角度 + """ + return AnyAngle(-self.radian % (2 * PI), is_radian=True) + + def __add__(self, other: "AnyAngle") -> "AnyAngle": + return AnyAngle(self.radian + other.radian, is_radian=True) + + def __sub__(self, other: "AnyAngle") -> "AnyAngle": + return AnyAngle(self.radian - other.radian, is_radian=True) + + def __mul__(self, other: float) -> "AnyAngle": + return AnyAngle(self.radian * other, is_radian=True) + + @overload + def __truediv__(self, other: float) -> "AnyAngle": + ... + + @overload + def __truediv__(self, other: "AnyAngle") -> float: + ... + + def __truediv__(self, other): + if isinstance(other, AnyAngle): + return self.radian / other.radian + return AnyAngle(self.radian / other, is_radian=True) diff --git a/mbcp/mp_math/equation.py b/mbcp/mp_math/equation.py index efecc63..9352af0 100644 --- a/mbcp/mp_math/equation.py +++ b/mbcp/mp_math/equation.py @@ -46,13 +46,15 @@ class CurveEquation: def get_partial_derivative_func(func: MultiVarsFunc, var: int | tuple[int, ...], epsilon: Number = EPSILON) -> MultiVarsFunc: """ - 求N元函数一阶偏导函数。 + 求N元函数一阶偏导函数。这玩意不太稳定,慎用。 Args: func: 函数 var: 变量位置,可为整数(一阶偏导)或整数元组(高阶偏导) epsilon: 偏移量 Returns: 偏导函数 + Raises: + ValueError: 无效变量类型 """ if isinstance(var, int): def partial_derivative_func(*args: Var) -> Var: diff --git a/mbcp/mp_math/line.py b/mbcp/mp_math/line.py index 8bcc38a..07f9537 100644 --- a/mbcp/mp_math/line.py +++ b/mbcp/mp_math/line.py @@ -5,49 +5,67 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @Time : 2024/8/6 下午12:57 @Author : snowykami @Email : snowykami@outlook.com -@File : line.py +@File : other.py @Software: PyCharm """ -from typing import TYPE_CHECKING +import math +from typing import TYPE_CHECKING, overload +from .vector import Vector3 if TYPE_CHECKING: - from .point import Point3 # type: ignore + from .angle import AnyAngle + from .plane import Plane3 + from .point import Point3 class Line3: def __init__(self, a: float, b: float, c: float, d: float): """ 三维空间中的直线。 - :param a: - :param b: - :param c: - :param d: + Args: + a: 直线方程的系数a + b: 直线方程的系数b + c: 直线方程的系数c + d: 直线方程的常数项d """ self.a = a self.b = b self.c = c self.d = d - def __str__(self): - return f"Line3({self.a}, {self.b}, {self.c}, {self.d})" + def cal_angle(self, other: "Line3") -> "AnyAngle": + """ + 计算直线和直线或面之间的夹角。 + Args: + other: 另一条直线或面 + Returns: + 夹角弧度 + Raises: + TypeError: 不支持的类型 + """ + if isinstance(other, Line3): + return self.direction.cal_angle(other.direction) + elif isinstance(other, Plane3): + return self.direction.cal_angle(other.normal).complementary # 方向向量和法向量的夹角的余角 + else: + raise TypeError(f"Unsupported type: {type(other)}") - def get_perpendicular(self, point: "Point3") -> "Line3": + @property + def direction(self) -> "Vector3": """ - 获取直线经过指定点p的垂线。 - :param point: 指定点p,直线外的点 - :return: 垂直于self且过点p的直线 + 直线的方向向量。 + Returns: + 方向向量 """ - a = -self.b - b = self.a - c = 0 - d = -(a * point.x + b * point.y + self.c * point.z) - return Line3(a, b, c, d) + return Vector3(self.a, self.b, self.c) - def get_intersection(self, line: "Line3") -> "Point3": + def cal_intersection(self, line: "Line3") -> "Point3": """ - 获取两条直线的交点。 - :param line: - :return: + 计算两条直线的交点。 + Args: + line: 另一条直线 + Returns: + 交点 """ if self.is_parallel(line): @@ -70,21 +88,39 @@ class Line3: return Point3(x, y, z) + def cal_perpendicular(self, point: "Point3") -> "Line3": + """ + 计算直线经过指定点p的垂线。 + Args: + point: 指定点 + Returns: + 垂线 + """ + a = -self.b + b = self.a + c = 0 + d = -(a * point.x + b * point.y + self.c * point.z) + return Line3(a, b, c, d) + def is_parallel(self, line: "Line3") -> bool: """ 判断两条直线是否平行。 直线平行的条件是它们的法向量成比例 - :param line: - :return: + Args: + line: 另一条直线 + Returns: + 是否平行 """ - return self.a * line.b == self.b * line.a and self.c * line.b == self.d * line.a + return self.direction.is_parallel(line.direction) def is_collinear(self, line: "Line3") -> bool: """ 判断两条直线是否共线。 直线共线的条件是它们的法向量成比例且常数项也成比例 - :param line: - :return: + Args: + line: 另一条直线 + Returns: + 是否共线 """ return self.is_parallel(line) and (self.d * line.b - self.b * line.d) / (self.a * line.b - self.b * line.a) == 0 @@ -92,10 +128,56 @@ class Line3: """ 判断两条直线是否共面。 两条直线共面的条件是它们的方向向量和法向量的叉乘为零向量 - :param line: - :return: + Args: + line: 另一条直线 + Returns: + 是否共面 """ direction1 = (-self.c, 0, self.a) direction2 = (line.c, -line.b, 0) cross_product = direction1[0] * direction2[1] - direction1[1] * direction2[0] return cross_product == 0 + + @classmethod + def from_point_and_direction(cls, point: "Point3", direction: "Vector3") -> "Line3": + """ + 工厂函数 由点和方向向量构造直线(点向式构造)。 + Args: + point: 点 + direction: 方向向量 + Returns: + 直线 + """ + a, b, c = direction.x, direction.y, direction.z + d = -(a * point.x + b * point.y + c * point.z) + return cls(a, b, c, d) + + @classmethod + def from_two_points(cls, p1: "Point3", p2: "Point3") -> "Line3": + """ + 工厂函数 由两点构造直线。 + Args: + p1: 点1 + p2: 点2 + Returns: + 直线 + """ + direction = p2 - p1 + return cls.from_point_and_direction(p1, direction) + + def __eq__(self, other) -> bool: + """ + 判断两条直线是否等价。 + Args: + other: + + Returns: + + """ + return self.a / other.a == self.b / other.b == self.c / other.c == self.d / other.d + + def __repr__(self): + return f"Line3({self.a}, {self.b}, {self.c}, {self.d})" + + def __str__(self): + return f"Line3({self.a}, {self.b}, {self.c}, {self.d})" diff --git a/mbcp/mp_math/plane.py b/mbcp/mp_math/plane.py index d8fc4e6..5041303 100644 --- a/mbcp/mp_math/plane.py +++ b/mbcp/mp_math/plane.py @@ -1,10 +1,124 @@ # -*- coding: utf-8 -*- """ -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved +平面模块 +""" +import math +from typing import TYPE_CHECKING -@Time : 2024/8/6 下午1:45 -@Author : snowykami -@Email : snowykami@outlook.com -@File : plane.py -@Software: PyCharm -""" \ No newline at end of file +import numpy as np + +from .vector import Vector3 +from .line import Line3 +from .point import Point3 + +if TYPE_CHECKING: + from .angle import AnyAngle + + +class Plane3: + def __init__(self, a: float, b: float, c: float, d: float): + """ + 平面方程:ax + by + cz + d = 0 + Args: + a: + b: + c: + d: + """ + self.a = a + self.b = b + self.c = c + self.d = d + + def cal_angle(self, other: "Line3 | Plane3") -> "AnyAngle": + """ + 计算平面与平面之间的夹角。 + Args: + other: 另一个平面 + Returns: + 夹角弧度 + Raises: + TypeError: 不支持的类型 + """ + if isinstance(other, Line3): + return self.normal.cal_angle(other.direction).complementary + elif isinstance(other, Plane3): + return AnyAngle(math.acos(self.normal * other.normal / (self.normal.length * other.normal.length)), is_radian=True) + else: + raise TypeError(f"Unsupported type: {type(other)}") + + def cal_distance(self, other: "Plane3 | Point3") -> float: + """ + 计算平面与平面或点之间的距离。 + Args: + other: 另一个平面或点 + Returns: + 距离 + Raises: + TypeError: 不支持的类型 + """ + if isinstance(other, Plane3): + return 0 + elif isinstance(other, Point3): + return abs(self.a * other.x + self.b * other.y + self.c * other.z + self.d) / (self.a ** 2 + self.b ** 2 + self.c ** 2) ** 0.5 + else: + raise TypeError(f"Unsupported type: {type(other)}") + + def cal_intersection_line3(self, other: "Plane3") -> "Line3": + """ + 计算两平面的交线。该方法有问题,待修复。 + Args: + other: 另一个平面 + Returns: + 交线 + """ + # 计算两法向量的叉积作为交线的方向向量 + s = self.normal.cross(other.normal) # 交线的方向向量 + # 联立两平面方程求交线的一点 + # 两平面方程联立得到的方程组 + # | a1x + b1y + c1z = -d1 + # | a2x + b2y + c2z = -d2 + # 用numpy解方程组 + a = np.array([[self.a, self.b, self.c], [other.a, other.b, other.c]]) + b = np.array([-self.d, -other.d]) + p = np.linalg.lstsq(a, b, rcond=None)[0] + return Line3.from_point_and_direction(Point3(*p), s) + + def cal_parallel_plane3(self, point: "Point3") -> "Plane3": + """ + 计算平行于该平面且过指定点的平面。 + Args: + point: 指定点 + Returns: + 平面 + """ + return Plane3.from_point_and_normal(point, self.normal) + + @property + def normal(self) -> "Vector3": + """ + 平面的法向量。 + Returns: + 法向量 + """ + return Vector3(self.a, self.b, self.c) + + @classmethod + def from_point_and_normal(cls, point: "Point3", normal: "Vector3") -> "Plane3": + """ + 工厂函数 由点和法向量构造平面(点法式构造)。 + Args: + point: 平面上的一点 + normal: 法向量 + Returns: + 平面 + """ + a, b, c = normal.x, normal.y, normal.z + d = -a * point.x - b * point.y - c * point.z # d = -ax - by - cz + return cls(a, b, c, d) + + def __repr__(self): + return f"Plane3({self.a}, {self.b}, {self.c}, {self.d})" + + def __str__(self): + return f"{self.a}x + {self.b}y + {self.c}z + {self.d} = 0" diff --git a/mbcp/mp_math/point.py b/mbcp/mp_math/point.py index bfd5ed6..4583994 100644 --- a/mbcp/mp_math/point.py +++ b/mbcp/mp_math/point.py @@ -44,6 +44,7 @@ class Point3: :param other: :return: """ + from .vector import Vector3 return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) def __truediv__(self, other: float) -> "Point3": diff --git a/mbcp/mp_math/py.typed b/mbcp/mp_math/py.typed index a8a7f97..e69de29 100644 --- a/mbcp/mp_math/py.typed +++ b/mbcp/mp_math/py.typed @@ -1,2 +0,0 @@ -# py.typed -partial \ No newline at end of file diff --git a/mbcp/mp_math/vector.py b/mbcp/mp_math/vector.py index fa987fd..ed2c3c3 100644 --- a/mbcp/mp_math/vector.py +++ b/mbcp/mp_math/vector.py @@ -1,9 +1,10 @@ +import math from typing import overload, TYPE_CHECKING -import numpy as np +from .point import Point3 if TYPE_CHECKING: - from .point import Point3 # type: ignore + from .angle import AnyAngle class Vector3: @@ -14,75 +15,59 @@ class Vector3: :param y: :param z: """ - self._x = x - self._y = y - self._z = z - self._length = (x ** 2 + y ** 2 + z ** 2) ** 0.5 - self._normalized = self / self._length + self.x = x + self.y = y + self.z = z - def __str__(self): - return f"Vector3({self._x}, {self._y}, {self._z})" + def cal_angle(self, other: 'Vector3') -> 'AnyAngle': + """ + 计算两个向量之间的夹角。 + Args: + other: 另一个向量 + Returns: + 夹角 + """ + return AnyAngle(math.acos(self * other / (self.length * other.length)), is_radian=True) - def _unset_properties(self): - self._length = None - self._normalized = None + def is_parallel(self, other: 'Vector3') -> bool: + """ + 判断两个向量是否平行。 + Args: + other: 另一个向量 + Returns: + 是否平行 + """ + return self @ other == Vector3(0, 0, 0) - @property - def x(self): - return self._x - - @x.setter - def x(self, value): - self._x = value - self._unset_properties() - - @property - def y(self): - return self._y - - @y.setter - def y(self, value): - self._y = value - self._unset_properties() - - @property - def z(self): - return self._z - - @z.setter - def z(self, value): - self._z = value - self._unset_properties() + def cross(self, other: 'Vector3') -> 'Vector3': + """ + 向量积 叉乘:V1 @ V2 -> V3 + Args: + other: + Returns: + 叉乘结果,为0向量则两向量平行,否则垂直于两向量 + """ + return Vector3(self.y * other.z - self.z * other.y, + self.z * other.x - self.x * other.z, + self.x * other.y - self.y * other.x) @property def length(self) -> float: """ 向量的模。 - :return: + Returns: + 模 """ - if self._length is None: - self._length = (self._x ** 2 + self._y ** 2 + self._z ** 2) ** 0.5 - return self._length + return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) @property - def normalized(self) -> 'Vector3': + def unit(self) -> 'Vector3': """ - 返回该向量的单位向量。 - :return: + 获取该向量的单位向量。 + Returns: + 单位向量 """ - if self._normalized is None: - self._normalized = self / self.length - return self._normalized - - def normalize(self): - """ - 自体归一化。 - """ - self._x /= self.length - self._y /= self.length - self._z /= self.length - self._length = 1 - self._normalized = self + return self / self.length @overload def __add__(self, other: 'Vector3') -> 'Vector3': @@ -96,16 +81,28 @@ class Vector3: """ V + P -> P\n V + V -> V - :param other: - :return: + Args: + other: + Returns: + """ if isinstance(other, Vector3): - return Vector3(self._x + other.x, self._y + other.y, self._z + other.z) + return Vector3(self.x + other.x, self.y + other.y, self.z + other.z) elif isinstance(other, Point3): - return Point3(self._x + other.x, self._y + other.y, self._z + other.z) + return Point3(self.x + other.x, self.y + other.y, self.z + other.z) else: raise TypeError(f"unsupported operand type(s) for +: 'Vector3' and '{type(other)}'") + def __eq__(self, other): + """ + 判断两个向量是否相等。 + Args: + other: + Returns: + 是否相等 + """ + return self.x == other.x and self.y == other.y and self.z == other.z + def __radd__(self, other: 'Point3') -> 'Point3': """ P + V -> P\n @@ -113,7 +110,7 @@ class Vector3: :param other: :return: """ - return Point3(self._x + other.x, self._y + other.y, self._z + other.z) + return Point3(self.x + other.x, self.y + other.y, self.z + other.z) @overload def __sub__(self, other: 'Vector3') -> 'Vector3': @@ -127,25 +124,28 @@ class Vector3: """ V - P -> P\n V - V -> V - :param other: - :return: + Args: + other: + Returns: """ if isinstance(other, Vector3): - return Vector3(self._x - other.x, self._y - other.y, self._z - other.z) + return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) elif isinstance(other, Point3): - return Point3(self._x - other.x, self._y - other.y, self._z - other.z) + return Point3(self.x - other.x, self.y - other.y, self.z - other.z) else: raise TypeError(f"unsupported operand type(s) for -: 'Vector3' and '{type(other)}'") def __rsub__(self, other: Point3): """ P - V -> P - :param other: - :return: + Args: + other: + Returns: + """ if isinstance(other, Point3): - return Point3(other.x - self._x, other.y - self._y, other.z - self._z) + return Point3(other.x - self.x, other.y - self.y, other.z - self.z) else: raise TypeError(f"unsupported operand type(s) for -: '{type(other)}' and 'Vector3'") @@ -159,34 +159,52 @@ class Vector3: def __mul__(self, other): """ - 乘法。包括点乘和数乘。 - :param other: - :return: + 点乘法。包括点乘和数乘。 + V * V -> float\n + Args: + other: + Returns: + float + Raises: + TypeError: 不支持的类型 """ if isinstance(other, (int, float)): - return Vector3(self._x * other, self._y * other, self._z * other) + return Vector3(self.x * other, self.y * other, self.z * other) elif isinstance(other, Vector3): - return self._x * other.x + self._y * other.y + self._z * other.z + return self.x * other.x + self.y * other.y + self.z * other.z else: raise TypeError(f"unsupported operand type(s) for *: 'Vector3' and '{type(other)}'") def __rmul__(self, other: float) -> 'Vector3': """ 右乘。 - :param other: - :return: + Args: + other: + Returns: + 乘积 """ - return Vector3(self._x * other, self._y * other, self._z * other) + return Vector3(self.x * other, self.y * other, self.z * other) def __matmul__(self, other: 'Vector3') -> 'Vector3': """ - 叉乘。 - :param other: 另一个向量 - :return: 叉乘结果向量 + 向量积 叉乘:V1 @ V2 -> V3 + Args: + other: + Returns: + 叉乘结果,为0向量则两向量平行,否则垂直于两向量 """ - return Vector3(self._y * other.z - self._z * other.y, - self._z * other.x - self._x * other.z, - self._x * other.y - self._y * other.x) + return Vector3(self.y * other.z - self.z * other.y, + self.z * other.x - self.x * other.z, + self.x * other.y - self.y * other.x) def __truediv__(self, other: float) -> 'Vector3': - return Vector3(self._x / other, self._y / other, self._z / other) + return Vector3(self.x / other, self.y / other, self.z / other) + + def __neg__(self): + return Vector3(-self.x, -self.y, -self.z) + + def __repr__(self): + return f"Vector3({self.x}, {self.y}, {self.z})" + + def __str__(self): + return f"Vector3({self.x}, {self.y}, {self.z})" diff --git a/mbcp/py.typed b/mbcp/py.typed index a8a7f97..e69de29 100644 --- a/mbcp/py.typed +++ b/mbcp/py.typed @@ -1,2 +0,0 @@ -# py.typed -partial \ No newline at end of file diff --git a/tests/test_line3.py b/tests/test_line3.py new file mode 100644 index 0000000..9ea3c3c --- /dev/null +++ b/tests/test_line3.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/26 上午7:54 +@Author : snowykami +@Email : snowykami@outlook.com +@File : test_line3.py +@Software: PyCharm +""" +import logging + +from mbcp.mp_math.point import Point3 +from mbcp.mp_math.vector import Vector3 +from mbcp.mp_math.line import Line3 + + +class TestLine3: + + def test_point_and_normal_factory(self): + """ + 测试通过点和法向量构造直线 + """ + correct_ans = Line3(1, -2, 3, -8) + + p = Point3(2, -3, 0) + n = Vector3(1, -2, 3) + + actual_ans = Line3.from_point_and_direction(p, n) + logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}") + assert actual_ans == correct_ans + + diff --git a/tests/test_plane3.py b/tests/test_plane3.py new file mode 100644 index 0000000..2729177 --- /dev/null +++ b/tests/test_plane3.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/26 上午9:07 +@Author : snowykami +@Email : snowykami@outlook.com +@File : test_plane3.py +@Software: PyCharm +""" +import logging + +from mbcp.mp_math.line import Line3 +from mbcp.mp_math.plane import Plane3 + + +class TestPlane3: + + def test_intersection_line3(self): + """ + 测试平面的交线 + """ + correct_ans = Line3(4, 3, 1, 1) + + pl1 = Plane3(1, 0, -4, 23) + pl2 = Plane3(2, -1, -5, 33) + actual_ans = pl1.cal_intersection_line3(pl2) + logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}") + assert actual_ans == correct_ans diff --git a/tests/test_vector.py b/tests/test_vector.py deleted file mode 100644 index f2bcc97..0000000 --- a/tests/test_vector.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/6 下午1:05 -@Author : snowykami -@Email : snowykami@outlook.com -@File : test_vector.py -@Software: PyCharm -""" -from mbcp.mp_math.vector import Vector3 -from mbcp.mp_math.point import Point3 - - -def test_v(): - v1 = Vector3(1, 2, 3) - v2 = Vector3(4, 5, 6) - v3 = v1 + v2 - assert v3._x == 5 - assert v3._y == 7 - assert v3._z == 9 - print("test_v 1111passed") diff --git a/tests/test_vector3.py b/tests/test_vector3.py new file mode 100644 index 0000000..e7f737e --- /dev/null +++ b/tests/test_vector3.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/26 上午6:58 +@Author : snowykami +@Email : snowykami@outlook.com +@File : test_question_1.py +@Software: PyCharm +""" +import logging + +from mbcp.mp_math.vector import Vector3 + + +class TestVector3: + + """测试问题集""" + def test_vector_cross_product(self): + """ + 测试向量叉乘 + Returns: + """ + v1 = Vector3(1, 2, 3) + v2 = Vector3(3, 4, 5) + actual_ans = v1 @ v2 + correct_ans = Vector3(-2, 4, -2) + logging.info(f"正确答案{correct_ans} 实际答案{v1 @ v2}") + + assert correct_ans == actual_ans + + def test_determine_vector_parallel(self): + """ + 测试判断向量是否平行 + Returns: + """ + v1 = Vector3(1, 2, 3) + v2 = Vector3(3, 6, 9) + actual_ans = v1.is_parallel(v2) + correct_ans = True + logging.info("v1和v2是否平行:%s", v1.is_parallel(v2)) + assert correct_ans == actual_ans + + v1 = Vector3(1, 2, 3) + v2 = Vector3(3, 6, 8) + actual_ans = v1.is_parallel(v2) + correct_ans = False + logging.info("v1和v2是否平行:%s", v1.is_parallel(v2)) + assert correct_ans == actual_ans + + + diff --git a/tests/test_word_problem.py b/tests/test_word_problem.py new file mode 100644 index 0000000..72f09b5 --- /dev/null +++ b/tests/test_word_problem.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +""" +应用题测试集 +""" +import logging + +from mbcp.mp_math.line import Line3 +from mbcp.mp_math.plane import Plane3 +from mbcp.mp_math.point import Point3 + + +class TestWordProblem: + + def test_c8s4e4(self): + """ + 同济大学《高等数学》第八版 下册 第八章第四节例4 + 问题:求与两平面x-4z-3=0和2x-y-5z-1=0的交线平行且过点(-3, 2, 5)的直线方程。 + """ + correct_ans = Line3(4, 3, 1, 1) + + pl1 = Plane3(1, 0, -4, -3) + pl2 = Plane3(2, -1, -5, -1) + p = Point3(-3, 2, 5) + """解法1""" + # 求直线方向向量s + s = pl1.normal @ pl2.normal + actual_ans = Line3.from_point_and_direction(p, s) + + logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}") + assert actual_ans == correct_ans + + """解法2""" + # 过点p且与pl1平行的平面pl3 + pl3 = pl1.cal_parallel_plane3(p) + # 过点p且与pl2平行的平面pl4 + pl4 = pl2.cal_parallel_plane3(p) + # 求pl3和pl4的交线 + actual_ans = pl3.cal_intersection_line3(pl4) + print(pl3, pl4, actual_ans) + + logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}") + assert actual_ans == correct_ans +