mirror of
https://github.com/snowykami/mbcp.git
synced 2024-11-22 06:07:37 +08:00
🐛 fix ε accuracy
This commit is contained in:
parent
5a0e2f189c
commit
39d056fb47
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,2 +1,8 @@
|
|||||||
*script*
|
*script*
|
||||||
.idea*
|
.idea*
|
||||||
|
|
||||||
|
# pdm toolchain
|
||||||
|
.pdm-build
|
||||||
|
.pdm-python
|
||||||
|
pdm.lock
|
||||||
|
.pdm-build/
|
189
main.py
189
main.py
@ -1,153 +1,46 @@
|
|||||||
# -*- coding: utf-8 -*-
|
from typing import overload
|
||||||
"""
|
|
||||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|
||||||
|
|
||||||
@Time : 2024/8/6 下午1:30
|
|
||||||
@Author : snowykami
|
|
||||||
@Email : snowykami@outlook.com
|
|
||||||
@File : main.py
|
|
||||||
@Software: PyCharm
|
|
||||||
"""
|
|
||||||
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:
|
class Vector:
|
||||||
return x ** 3 * y ** 2 - 3 * x * y ** 3 - x * y + 1
|
def __init__(self, x: float, y: float, z: float):
|
||||||
|
"""
|
||||||
|
向量
|
||||||
|
Args:
|
||||||
|
x: x轴分量
|
||||||
|
y: y轴分量
|
||||||
|
z: z轴分量
|
||||||
|
"""
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.z = z
|
||||||
|
@overload
|
||||||
|
def __mul__(self, other: float) -> 'Vector':
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __mul__(self, other: 'Vector') -> float:
|
||||||
|
...
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
"""
|
||||||
|
点乘和数乘
|
||||||
|
Args:
|
||||||
|
other:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if isinstance(other, (float, int)):
|
||||||
|
return Vector(self.x * other, self.y * other, self.z * other)
|
||||||
|
elif isinstance(other, Vector):
|
||||||
|
return self.x * other.x + self.y * other.y + self.z * other.z
|
||||||
|
else:
|
||||||
|
raise TypeError(f"unsupported operand type(s) for *: 'Vector' and '{type(other)}'")
|
||||||
|
|
||||||
|
def __rmul__(self, other: float) -> 'Vector':
|
||||||
|
return self.__mul__(other)
|
||||||
|
|
||||||
|
|
||||||
class TestPartialDerivative:
|
v: Vector = Vector(1, 2, 3) * 3.0
|
||||||
# 样例来源:同济大学《高等数学》第八版下册 第九章第二节 例6
|
v2: Vector = 3.0 * Vector(1, 2, 3)
|
||||||
def test_2v_1o_1v(self):
|
|
||||||
"""测试二元函数关于第一个变量(x)的一阶偏导 df/dx"""
|
|
||||||
|
|
||||||
from mbcp.mp_math.utils import Approx
|
print(v, v2)
|
||||||
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()
|
|
@ -10,7 +10,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
"""
|
"""
|
||||||
from typing import overload
|
from typing import overload
|
||||||
|
|
||||||
from .const import PI
|
from .const import PI # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class AnyAngle:
|
class AnyAngle:
|
||||||
@ -27,7 +27,7 @@ class AnyAngle:
|
|||||||
self.radian = value * PI / 180
|
self.radian = value * PI / 180
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def complementary(self) -> "AnyAngle":
|
def complementary(self) -> 'AnyAngle':
|
||||||
"""
|
"""
|
||||||
余角:两角的和为90°。
|
余角:两角的和为90°。
|
||||||
Returns:
|
Returns:
|
||||||
@ -36,7 +36,7 @@ class AnyAngle:
|
|||||||
return AnyAngle(PI / 2 - self.minimum_positive.radian, is_radian=True)
|
return AnyAngle(PI / 2 - self.minimum_positive.radian, is_radian=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supplementary(self) -> "AnyAngle":
|
def supplementary(self) -> 'AnyAngle':
|
||||||
"""
|
"""
|
||||||
补角:两角的和为180°。
|
补角:两角的和为180°。
|
||||||
Returns:
|
Returns:
|
||||||
@ -54,7 +54,7 @@ class AnyAngle:
|
|||||||
return self.radian * 180 / PI
|
return self.radian * 180 / PI
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def minimum_positive(self) -> "AnyAngle":
|
def minimum_positive(self) -> 'AnyAngle':
|
||||||
"""
|
"""
|
||||||
最小正角。
|
最小正角。
|
||||||
Returns:
|
Returns:
|
||||||
@ -63,7 +63,7 @@ class AnyAngle:
|
|||||||
return AnyAngle(self.radian % (2 * PI))
|
return AnyAngle(self.radian % (2 * PI))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def maximum_negative(self) -> "AnyAngle":
|
def maximum_negative(self) -> 'AnyAngle':
|
||||||
"""
|
"""
|
||||||
最大负角。
|
最大负角。
|
||||||
Returns:
|
Returns:
|
||||||
@ -71,21 +71,21 @@ class AnyAngle:
|
|||||||
"""
|
"""
|
||||||
return AnyAngle(-self.radian % (2 * PI), is_radian=True)
|
return AnyAngle(-self.radian % (2 * PI), is_radian=True)
|
||||||
|
|
||||||
def __add__(self, other: "AnyAngle") -> "AnyAngle":
|
def __add__(self, other: 'AnyAngle') -> 'AnyAngle':
|
||||||
return AnyAngle(self.radian + other.radian, is_radian=True)
|
return AnyAngle(self.radian + other.radian, is_radian=True)
|
||||||
|
|
||||||
def __sub__(self, other: "AnyAngle") -> "AnyAngle":
|
def __sub__(self, other: 'AnyAngle') -> 'AnyAngle':
|
||||||
return AnyAngle(self.radian - other.radian, is_radian=True)
|
return AnyAngle(self.radian - other.radian, is_radian=True)
|
||||||
|
|
||||||
def __mul__(self, other: float) -> "AnyAngle":
|
def __mul__(self, other: float) -> 'AnyAngle':
|
||||||
return AnyAngle(self.radian * other, is_radian=True)
|
return AnyAngle(self.radian * other, is_radian=True)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __truediv__(self, other: float) -> "AnyAngle":
|
def __truediv__(self, other: float) -> 'AnyAngle':
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __truediv__(self, other: "AnyAngle") -> float:
|
def __truediv__(self, other: 'AnyAngle') -> float:
|
||||||
...
|
...
|
||||||
|
|
||||||
def __truediv__(self, other):
|
def __truediv__(self, other):
|
||||||
|
@ -8,87 +8,55 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@File : other.py
|
@File : other.py
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
import math
|
from typing import TYPE_CHECKING
|
||||||
from typing import TYPE_CHECKING, overload
|
|
||||||
|
from .mp_math_typing import OneSingleVarFunc, RealNumber
|
||||||
|
from .utils import sign_format
|
||||||
from .vector import Vector3
|
from .vector import Vector3
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .angle import AnyAngle
|
from .angle import AnyAngle
|
||||||
from .plane import Plane3
|
|
||||||
from .point import Point3
|
from .point import Point3
|
||||||
|
|
||||||
|
|
||||||
class Line3:
|
class Line3:
|
||||||
def __init__(self, a: float, b: float, c: float, d: float):
|
def __init__(self, point: 'Point3', direction: 'Vector3'):
|
||||||
"""
|
"""
|
||||||
三维空间中的直线。
|
三维空间中的直线。由一个点和一个方向向量确定。
|
||||||
Args:
|
Args:
|
||||||
a: 直线方程的系数a
|
point: 直线上的一点
|
||||||
b: 直线方程的系数b
|
direction: 直线的方向向量
|
||||||
c: 直线方程的系数c
|
|
||||||
d: 直线方程的常数项d
|
|
||||||
"""
|
"""
|
||||||
self.a = a
|
self.point = point
|
||||||
self.b = b
|
self.direction = direction
|
||||||
self.c = c
|
|
||||||
self.d = d
|
|
||||||
|
|
||||||
def cal_angle(self, other: "Line3") -> "AnyAngle":
|
def cal_angle(self, other: 'Line3') -> 'AnyAngle':
|
||||||
"""
|
"""
|
||||||
计算直线和直线或面之间的夹角。
|
计算直线和直线之间的夹角。
|
||||||
Args:
|
Args:
|
||||||
other: 另一条直线或面
|
other: 另一条直线
|
||||||
Returns:
|
Returns:
|
||||||
夹角弧度
|
夹角弧度
|
||||||
Raises:
|
Raises:
|
||||||
TypeError: 不支持的类型
|
TypeError: 不支持的类型
|
||||||
"""
|
"""
|
||||||
if isinstance(other, Line3):
|
return self.direction.cal_angle(other.direction)
|
||||||
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)}")
|
|
||||||
|
|
||||||
@property
|
def cal_intersection(self, other: 'Line3') -> 'Point3':
|
||||||
def direction(self) -> "Vector3":
|
|
||||||
"""
|
|
||||||
直线的方向向量。
|
|
||||||
Returns:
|
|
||||||
方向向量
|
|
||||||
"""
|
|
||||||
return Vector3(self.a, self.b, self.c)
|
|
||||||
|
|
||||||
def cal_intersection(self, line: "Line3") -> "Point3":
|
|
||||||
"""
|
"""
|
||||||
计算两条直线的交点。
|
计算两条直线的交点。
|
||||||
Args:
|
Args:
|
||||||
line: 另一条直线
|
other: 另一条直线
|
||||||
Returns:
|
Returns:
|
||||||
交点
|
交点
|
||||||
"""
|
"""
|
||||||
|
if self.is_parallel(other):
|
||||||
if self.is_parallel(line):
|
|
||||||
raise ValueError("Lines are parallel and do not intersect.")
|
raise ValueError("Lines are parallel and do not intersect.")
|
||||||
|
if not self.is_coplanar(other):
|
||||||
if self.is_collinear(line):
|
|
||||||
raise ValueError("Lines are collinear and do not have a single intersection point.")
|
|
||||||
|
|
||||||
if not self.is_coplanar(line):
|
|
||||||
raise ValueError("Lines are not coplanar and do not intersect.")
|
raise ValueError("Lines are not coplanar and do not intersect.")
|
||||||
|
return self.point + self.direction.cross(other.direction)
|
||||||
|
|
||||||
a1, b1, c1, d1 = self.a, self.b, self.c, self.d
|
def cal_perpendicular(self, point: 'Point3') -> 'Line3':
|
||||||
a2, b2, c2, d2 = line.a, line.b, line.c, line.d
|
|
||||||
|
|
||||||
t = (b1 * (c2 * d1 - c1 * d2) - b2 * (c1 * d1 - c2 * d2)) / (b1 * c2 - b2 * c1)
|
|
||||||
|
|
||||||
x = self.a * t + self.b * (-d1 / self.b)
|
|
||||||
y = -self.b * t + self.a * (d1 / self.a)
|
|
||||||
z = 0
|
|
||||||
|
|
||||||
return Point3(x, y, z)
|
|
||||||
|
|
||||||
def cal_perpendicular(self, point: "Point3") -> "Line3":
|
|
||||||
"""
|
"""
|
||||||
计算直线经过指定点p的垂线。
|
计算直线经过指定点p的垂线。
|
||||||
Args:
|
Args:
|
||||||
@ -96,64 +64,78 @@ class Line3:
|
|||||||
Returns:
|
Returns:
|
||||||
垂线
|
垂线
|
||||||
"""
|
"""
|
||||||
a = -self.b
|
return Line3(point, self.direction.cross(point - self.point))
|
||||||
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:
|
def get_point(self, t: RealNumber) -> 'Point3':
|
||||||
|
"""
|
||||||
|
获取直线上的点。同一条直线,但起始点和方向向量不同,则同一个t对应的点不同。
|
||||||
|
Args:
|
||||||
|
t: 参数t
|
||||||
|
Returns:
|
||||||
|
点
|
||||||
|
"""
|
||||||
|
return self.point + t * self.direction
|
||||||
|
|
||||||
|
def get_parametric_equations(self) -> tuple[OneSingleVarFunc, OneSingleVarFunc, OneSingleVarFunc]:
|
||||||
|
"""
|
||||||
|
获取直线的参数方程。
|
||||||
|
Returns:
|
||||||
|
x(t), y(t), z(t)
|
||||||
|
"""
|
||||||
|
return (lambda t: self.point.x + self.direction.x * t,
|
||||||
|
lambda t: self.point.y + self.direction.y * t,
|
||||||
|
lambda t: self.point.z + self.direction.z * t)
|
||||||
|
|
||||||
|
def is_parallel(self, other: 'Line3') -> bool:
|
||||||
"""
|
"""
|
||||||
判断两条直线是否平行。
|
判断两条直线是否平行。
|
||||||
直线平行的条件是它们的法向量成比例
|
|
||||||
Args:
|
Args:
|
||||||
line: 另一条直线
|
other: 另一条直线
|
||||||
Returns:
|
Returns:
|
||||||
是否平行
|
是否平行
|
||||||
"""
|
"""
|
||||||
return self.direction.is_parallel(line.direction)
|
return self.direction.is_parallel(other.direction)
|
||||||
|
|
||||||
def is_collinear(self, line: "Line3") -> bool:
|
def is_collinear(self, other: 'Line3') -> bool:
|
||||||
"""
|
"""
|
||||||
判断两条直线是否共线。
|
判断两条直线是否共线。
|
||||||
直线共线的条件是它们的法向量成比例且常数项也成比例
|
|
||||||
Args:
|
Args:
|
||||||
line: 另一条直线
|
other: 另一条直线
|
||||||
Returns:
|
Returns:
|
||||||
是否共线
|
是否共线
|
||||||
"""
|
"""
|
||||||
return self.is_parallel(line) and (self.d * line.b - self.b * line.d) / (self.a * line.b - self.b * line.a) == 0
|
return self.is_parallel(other) and (self.point - other.point).is_parallel(self.direction)
|
||||||
|
|
||||||
def is_coplanar(self, line: "Line3") -> bool:
|
def is_coplanar(self, other: 'Line3') -> bool:
|
||||||
"""
|
"""
|
||||||
判断两条直线是否共面。
|
判断两条直线是否共面。
|
||||||
两条直线共面的条件是它们的方向向量和法向量的叉乘为零向量
|
|
||||||
Args:
|
Args:
|
||||||
line: 另一条直线
|
other: 另一条直线
|
||||||
Returns:
|
Returns:
|
||||||
是否共面
|
是否共面
|
||||||
"""
|
"""
|
||||||
direction1 = (-self.c, 0, self.a)
|
return self.direction.cross(other.direction).is_parallel(self.direction)
|
||||||
direction2 = (line.c, -line.b, 0)
|
|
||||||
cross_product = direction1[0] * direction2[1] - direction1[1] * direction2[0]
|
def simplify(self):
|
||||||
return cross_product == 0
|
"""
|
||||||
|
简化直线方程,等价相等。
|
||||||
|
自体简化,不返回值。
|
||||||
|
|
||||||
|
按照可行性一次对x y z 化 0 处理,并对向量单位化
|
||||||
|
"""
|
||||||
|
self.direction.normalize()
|
||||||
|
# 平行与zy平面,x始终为0
|
||||||
|
if self.direction.x == 0:
|
||||||
|
self.point.x = 0
|
||||||
|
# 平行与xz平面,y始终为0
|
||||||
|
if self.direction.y == 0:
|
||||||
|
self.point.y = 0
|
||||||
|
# 平行与xy平面,z始终为0
|
||||||
|
if self.direction.z == 0:
|
||||||
|
self.point.z = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_point_and_direction(cls, point: "Point3", direction: "Vector3") -> "Line3":
|
def from_two_points(cls, p1: 'Point3', p2: 'Point3') -> '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:
|
Args:
|
||||||
@ -163,21 +145,45 @@ class Line3:
|
|||||||
直线
|
直线
|
||||||
"""
|
"""
|
||||||
direction = p2 - p1
|
direction = p2 - p1
|
||||||
return cls.from_point_and_direction(p1, direction)
|
return cls(p1, direction)
|
||||||
|
|
||||||
|
def __and__(self, other: 'Line3') -> 'Point3':
|
||||||
|
"""
|
||||||
|
计算两条直线点集合的交集。交点
|
||||||
|
Args:
|
||||||
|
other: 另一条直线
|
||||||
|
Returns:
|
||||||
|
交点
|
||||||
|
"""
|
||||||
|
return self.cal_intersection(other)
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other) -> bool:
|
||||||
"""
|
"""
|
||||||
判断两条直线是否等价。
|
判断两条直线是否等价。
|
||||||
|
|
||||||
|
v1 // v2 ∧ (p1 - p2) // v1
|
||||||
Args:
|
Args:
|
||||||
other:
|
other:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.a / other.a == self.b / other.b == self.c / other.c == self.d / other.d
|
return self.direction.is_parallel(other.direction) and (self.point - other.point).is_parallel(self.direction)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"Line3({self.a}, {self.b}, {self.c}, {self.d})"
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Line3({self.a}, {self.b}, {self.c}, {self.d})"
|
"""
|
||||||
|
返回点向式(x-x0)
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
s = "Line3: "
|
||||||
|
if self.direction.x != 0:
|
||||||
|
s += f"(x{sign_format(-self.point.x)})/{self.direction.x}"
|
||||||
|
if self.direction.y != 0:
|
||||||
|
s += f" = (y{sign_format(-self.point.y)})/{self.direction.y}"
|
||||||
|
if self.direction.z != 0:
|
||||||
|
s += f" = (z{sign_format(-self.point.z)})/{self.direction.z}"
|
||||||
|
return s
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Line3({self.point}, {self.direction})"
|
||||||
|
@ -3,13 +3,14 @@
|
|||||||
平面模块
|
平面模块
|
||||||
"""
|
"""
|
||||||
import math
|
import math
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, overload
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from .vector import Vector3
|
from .vector import Vector3, zero_vector3
|
||||||
from .line import Line3
|
from .line import Line3
|
||||||
from .point import Point3
|
from .point import Point3
|
||||||
|
from .utils import sign
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .angle import AnyAngle
|
from .angle import AnyAngle
|
||||||
@ -30,7 +31,7 @@ class Plane3:
|
|||||||
self.c = c
|
self.c = c
|
||||||
self.d = d
|
self.d = d
|
||||||
|
|
||||||
def cal_angle(self, other: "Line3 | Plane3") -> "AnyAngle":
|
def cal_angle(self, other: 'Line3 | Plane3') -> 'AnyAngle':
|
||||||
"""
|
"""
|
||||||
计算平面与平面之间的夹角。
|
计算平面与平面之间的夹角。
|
||||||
Args:
|
Args:
|
||||||
@ -43,11 +44,11 @@ class Plane3:
|
|||||||
if isinstance(other, Line3):
|
if isinstance(other, Line3):
|
||||||
return self.normal.cal_angle(other.direction).complementary
|
return self.normal.cal_angle(other.direction).complementary
|
||||||
elif isinstance(other, Plane3):
|
elif isinstance(other, Plane3):
|
||||||
return AnyAngle(math.acos(self.normal * other.normal / (self.normal.length * other.normal.length)), is_radian=True)
|
return AnyAngle(math.acos(self.normal @ other.normal / (self.normal.length * other.normal.length)), is_radian=True)
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"Unsupported type: {type(other)}")
|
raise TypeError(f"Unsupported type: {type(other)}")
|
||||||
|
|
||||||
def cal_distance(self, other: "Plane3 | Point3") -> float:
|
def cal_distance(self, other: 'Plane3 | Point3') -> float:
|
||||||
"""
|
"""
|
||||||
计算平面与平面或点之间的距离。
|
计算平面与平面或点之间的距离。
|
||||||
Args:
|
Args:
|
||||||
@ -64,27 +65,58 @@ class Plane3:
|
|||||||
else:
|
else:
|
||||||
raise TypeError(f"Unsupported type: {type(other)}")
|
raise TypeError(f"Unsupported type: {type(other)}")
|
||||||
|
|
||||||
def cal_intersection_line3(self, other: "Plane3") -> "Line3":
|
def cal_intersection_line3(self, other: 'Plane3') -> 'Line3':
|
||||||
"""
|
"""
|
||||||
计算两平面的交线。该方法有问题,待修复。
|
计算两平面的交线。该方法有问题,待修复。
|
||||||
Args:
|
Args:
|
||||||
other: 另一个平面
|
other: 另一个平面
|
||||||
Returns:
|
Returns:
|
||||||
交线
|
交线
|
||||||
|
Raises:
|
||||||
"""
|
"""
|
||||||
# 计算两法向量的叉积作为交线的方向向量
|
if self.normal.is_parallel(other.normal):
|
||||||
s = self.normal.cross(other.normal) # 交线的方向向量
|
raise ValueError("Planes are parallel and have no intersection.")
|
||||||
# 联立两平面方程求交线的一点
|
direction = self.normal.cross(other.normal) # 法向量叉乘得到方向向量
|
||||||
# 两平面方程联立得到的方程组
|
# 寻找直线上的一点,依次假设x=0, y=0, z=0,找到合适的点
|
||||||
# | a1x + b1y + c1z = -d1
|
x, y, z = 0, 0, 0
|
||||||
# | a2x + b2y + c2z = -d2
|
# 依次判断条件假设x=0, y=0, z=0,找到合适的点
|
||||||
# 用numpy解方程组
|
# 先假设其中一个系数不为0,则令此坐标为0,构建增广矩阵,解出另外两个坐标
|
||||||
a = np.array([[self.a, self.b, self.c], [other.a, other.b, other.c]])
|
if self.a != 0 and other.a != 0:
|
||||||
b = np.array([-self.d, -other.d])
|
A = np.array([[self.b, self.c], [other.b, other.c]])
|
||||||
p = np.linalg.lstsq(a, b, rcond=None)[0]
|
B = np.array([-self.d, -other.d])
|
||||||
return Line3.from_point_and_direction(Point3(*p), s)
|
y, z = np.linalg.solve(A, B)
|
||||||
|
elif self.b != 0 and other.b != 0:
|
||||||
|
A = np.array([[self.a, self.c], [other.a, other.c]])
|
||||||
|
B = np.array([-self.d, -other.d])
|
||||||
|
x, z = np.linalg.solve(A, B)
|
||||||
|
elif self.c != 0 and other.c != 0:
|
||||||
|
A = np.array([[self.a, self.b], [other.a, other.b]])
|
||||||
|
B = np.array([-self.d, -other.d])
|
||||||
|
x, y = np.linalg.solve(A, B)
|
||||||
|
|
||||||
def cal_parallel_plane3(self, point: "Point3") -> "Plane3":
|
return Line3(Point3(x, y, z), direction)
|
||||||
|
|
||||||
|
def cal_intersection_point3(self, other: 'Line3') -> 'Point3':
|
||||||
|
"""
|
||||||
|
计算平面与直线的交点。
|
||||||
|
Args:
|
||||||
|
other: 不与平面平行或在平面上的直线
|
||||||
|
Returns:
|
||||||
|
交点
|
||||||
|
Raises:
|
||||||
|
ValueError: 平面与直线平行或重合
|
||||||
|
"""
|
||||||
|
# 若平面的法向量与直线方向向量垂直,则直线与平面平行或重合
|
||||||
|
if self.normal @ other.direction == 0:
|
||||||
|
raise ValueError("The plane and the line are parallel or coincident.")
|
||||||
|
# 获取直线的参数方程
|
||||||
|
# 代入平面方程,解出t
|
||||||
|
x, y, z = other.get_parametric_equations()
|
||||||
|
t = (-(self.a * other.point.x + self.b * other.point.y + self.c * other.point.z + self.d) /
|
||||||
|
(self.a * other.direction.x + self.b * other.direction.y + self.c * other.direction.z))
|
||||||
|
return Point3(x(t), y(t), z(t))
|
||||||
|
|
||||||
|
def cal_parallel_plane3(self, point: 'Point3') -> 'Plane3':
|
||||||
"""
|
"""
|
||||||
计算平行于该平面且过指定点的平面。
|
计算平行于该平面且过指定点的平面。
|
||||||
Args:
|
Args:
|
||||||
@ -95,7 +127,7 @@ class Plane3:
|
|||||||
return Plane3.from_point_and_normal(point, self.normal)
|
return Plane3.from_point_and_normal(point, self.normal)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def normal(self) -> "Vector3":
|
def normal(self) -> 'Vector3':
|
||||||
"""
|
"""
|
||||||
平面的法向量。
|
平面的法向量。
|
||||||
Returns:
|
Returns:
|
||||||
@ -104,7 +136,7 @@ class Plane3:
|
|||||||
return Vector3(self.a, self.b, self.c)
|
return Vector3(self.a, self.b, self.c)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_point_and_normal(cls, point: "Point3", normal: "Vector3") -> "Plane3":
|
def from_point_and_normal(cls, point: 'Point3', normal: 'Vector3') -> 'Plane3':
|
||||||
"""
|
"""
|
||||||
工厂函数 由点和法向量构造平面(点法式构造)。
|
工厂函数 由点和法向量构造平面(点法式构造)。
|
||||||
Args:
|
Args:
|
||||||
@ -117,8 +149,93 @@ class Plane3:
|
|||||||
d = -a * point.x - b * point.y - c * point.z # d = -ax - by - cz
|
d = -a * point.x - b * point.y - c * point.z # d = -ax - by - cz
|
||||||
return cls(a, b, c, d)
|
return cls(a, b, c, d)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_three_points(cls, p1: 'Point3', p2: 'Point3', p3: 'Point3') -> 'Plane3':
|
||||||
|
"""
|
||||||
|
工厂函数 由三点构造平面。
|
||||||
|
Args:
|
||||||
|
p1: 点1
|
||||||
|
p2: 点2
|
||||||
|
p3: 点3
|
||||||
|
Returns:
|
||||||
|
平面
|
||||||
|
"""
|
||||||
|
# 两个向量
|
||||||
|
v1 = p2 - p1
|
||||||
|
v2 = p3 - p1
|
||||||
|
# 法向量
|
||||||
|
normal = v1.cross(v2)
|
||||||
|
return cls.from_point_and_normal(p1, normal)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_two_lines(cls, l1: 'Line3', l2: 'Line3') -> 'Plane3':
|
||||||
|
"""
|
||||||
|
工厂函数 由两直线构造平面。
|
||||||
|
Args:
|
||||||
|
l1: 直线1
|
||||||
|
l2: 直线2
|
||||||
|
Returns:
|
||||||
|
平面
|
||||||
|
"""
|
||||||
|
v1 = l1.direction
|
||||||
|
v2 = l2.point - l1.point
|
||||||
|
if v2 == zero_vector3:
|
||||||
|
v2 = l2.get_point(1) - l1.point
|
||||||
|
return cls.from_point_and_normal(l1.point, v1.cross(v2))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_point_and_line(cls, point: 'Point3', line: 'Line3') -> 'Plane3':
|
||||||
|
"""
|
||||||
|
工厂函数 由点和直线构造平面。
|
||||||
|
Args:
|
||||||
|
point: 面上一点
|
||||||
|
line: 面上直线,不包含点
|
||||||
|
Returns:
|
||||||
|
平面
|
||||||
|
"""
|
||||||
|
return cls.from_point_and_normal(point, line.direction)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Plane3({self.a}, {self.b}, {self.c}, {self.d})"
|
return f"Plane3({self.a}, {self.b}, {self.c}, {self.d})"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.a}x + {self.b}y + {self.c}z + {self.d} = 0"
|
s = "Plane3: "
|
||||||
|
if self.a != 0:
|
||||||
|
s += f"{sign(self.a, only_neg=True)}{abs(self.a)}x"
|
||||||
|
if self.b != 0:
|
||||||
|
s += f" {sign(self.b)} {abs(self.b)}y"
|
||||||
|
if self.c != 0:
|
||||||
|
s += f" {sign(self.c)} {abs(self.c)}z"
|
||||||
|
if self.d != 0:
|
||||||
|
s += f" {sign(self.d)} {abs(self.d)}"
|
||||||
|
return s + " = 0"
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __and__(self, other: 'Line3') -> 'Point3 | None':
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __and__(self, other: 'Plane3') -> 'Line3 | None':
|
||||||
|
...
|
||||||
|
|
||||||
|
def __and__(self, other):
|
||||||
|
"""
|
||||||
|
取两平面的交集(人话:交线)
|
||||||
|
Args:
|
||||||
|
other:
|
||||||
|
Returns:
|
||||||
|
不平行平面的交线,平面平行返回None
|
||||||
|
"""
|
||||||
|
if isinstance(other, Plane3):
|
||||||
|
if self.normal.is_parallel(other.normal):
|
||||||
|
return None
|
||||||
|
return self.cal_intersection_line3(other)
|
||||||
|
elif isinstance(other, Line3):
|
||||||
|
if self.normal @ other.direction == 0:
|
||||||
|
return None
|
||||||
|
return self.cal_intersection_point3(other)
|
||||||
|
else:
|
||||||
|
raise TypeError(f"unsupported operand type(s) for &: 'Plane3' and '{type(other)}'")
|
||||||
|
|
||||||
|
def __rand__(self, other: 'Line3') -> 'Point3':
|
||||||
|
return self.cal_intersection_point3(other)
|
||||||
|
@ -8,9 +8,10 @@ class Point3:
|
|||||||
def __init__(self, x: float, y: float, z: float):
|
def __init__(self, x: float, y: float, z: float):
|
||||||
"""
|
"""
|
||||||
笛卡尔坐标系中的点。
|
笛卡尔坐标系中的点。
|
||||||
:param x:
|
Args:
|
||||||
:param y:
|
x: x 坐标
|
||||||
:param z:
|
y: y 坐标
|
||||||
|
z: z 坐标
|
||||||
"""
|
"""
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
@ -31,26 +32,30 @@ class Point3:
|
|||||||
"""
|
"""
|
||||||
P + V -> P
|
P + V -> P
|
||||||
P + P -> P
|
P + P -> P
|
||||||
:param other:
|
Args:
|
||||||
:return:
|
other:
|
||||||
|
Returns:
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""
|
||||||
|
判断两个点是否相等。
|
||||||
|
Args:
|
||||||
|
other:
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
return self.x == other.x and self.y == other.y and self.z == other.z
|
||||||
|
|
||||||
def __sub__(self, other: "Point3") -> "Vector3":
|
def __sub__(self, other: "Point3") -> "Vector3":
|
||||||
"""
|
"""
|
||||||
P - P -> V
|
P - P -> V
|
||||||
|
|
||||||
P - V -> P 已在 :class:`Vector3` 中实现
|
P - V -> P 已在 :class:`Vector3` 中实现
|
||||||
:param other:
|
Args:
|
||||||
:return:
|
other:
|
||||||
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from .vector import Vector3
|
from .vector import 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)
|
||||||
|
|
||||||
def __truediv__(self, other: float) -> "Point3":
|
|
||||||
"""
|
|
||||||
P / n -> P
|
|
||||||
:param other:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return Point3(self.x / other, self.y / other, self.z / other)
|
|
||||||
|
@ -56,3 +56,38 @@ def approx(x: float, y: float = 0.0, epsilon: float = 0.0001) -> bool:
|
|||||||
是否近似相等
|
是否近似相等
|
||||||
"""
|
"""
|
||||||
return abs(x - y) < epsilon
|
return abs(x - y) < epsilon
|
||||||
|
|
||||||
|
|
||||||
|
def sign(x: float, only_neg: bool = False) -> str:
|
||||||
|
"""获取数的符号。
|
||||||
|
Args:
|
||||||
|
x: 数
|
||||||
|
only_neg: 是否只返回负数的符号
|
||||||
|
Returns:
|
||||||
|
符号 + - ""
|
||||||
|
"""
|
||||||
|
if x > 0:
|
||||||
|
return "+" if not only_neg else ""
|
||||||
|
elif x < 0:
|
||||||
|
return "-"
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def sign_format(x: float, only_neg: bool = False) -> str:
|
||||||
|
"""格式化符号数
|
||||||
|
-1 -> -1
|
||||||
|
1 -> +1
|
||||||
|
0 -> ""
|
||||||
|
Args:
|
||||||
|
x: 数
|
||||||
|
only_neg: 是否只返回负数的符号
|
||||||
|
Returns:
|
||||||
|
符号 + - ""
|
||||||
|
"""
|
||||||
|
if x > 0:
|
||||||
|
return f"+{x}" if not only_neg else f"{x}"
|
||||||
|
elif x < 0:
|
||||||
|
return f"-{abs(x)}"
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import math
|
import math
|
||||||
from typing import overload, TYPE_CHECKING
|
from typing import overload, TYPE_CHECKING
|
||||||
|
|
||||||
|
from .mp_math_typing import RealNumber
|
||||||
from .point import Point3
|
from .point import Point3
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -10,10 +11,11 @@ if TYPE_CHECKING:
|
|||||||
class Vector3:
|
class Vector3:
|
||||||
def __init__(self, x: float, y: float, z: float):
|
def __init__(self, x: float, y: float, z: float):
|
||||||
"""
|
"""
|
||||||
笛卡尔坐标系中的向量。
|
3维向量
|
||||||
:param x:
|
Args:
|
||||||
:param y:
|
x: x轴分量
|
||||||
:param z:
|
y: y轴分量
|
||||||
|
z: z轴分量
|
||||||
"""
|
"""
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
@ -27,7 +29,7 @@ class Vector3:
|
|||||||
Returns:
|
Returns:
|
||||||
夹角
|
夹角
|
||||||
"""
|
"""
|
||||||
return AnyAngle(math.acos(self * other / (self.length * other.length)), is_radian=True)
|
return AnyAngle(math.acos(self @ other / (self.length * other.length)), is_radian=True)
|
||||||
|
|
||||||
def is_parallel(self, other: 'Vector3') -> bool:
|
def is_parallel(self, other: 'Vector3') -> bool:
|
||||||
"""
|
"""
|
||||||
@ -37,20 +39,39 @@ class Vector3:
|
|||||||
Returns:
|
Returns:
|
||||||
是否平行
|
是否平行
|
||||||
"""
|
"""
|
||||||
return self @ other == Vector3(0, 0, 0)
|
return self.cross(other) == Vector3(0, 0, 0)
|
||||||
|
|
||||||
def cross(self, other: 'Vector3') -> 'Vector3':
|
def cross(self, other: 'Vector3') -> 'Vector3':
|
||||||
"""
|
"""
|
||||||
向量积 叉乘:V1 @ V2 -> V3
|
向量积 叉乘:v1 cross v2 -> v3
|
||||||
|
返回如下行列式的结果:
|
||||||
|
|
||||||
|
``i j k``
|
||||||
|
|
||||||
|
``x1 y1 z1``
|
||||||
|
|
||||||
|
``x2 y2 z2``
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
other:
|
other:
|
||||||
Returns:
|
Returns:
|
||||||
叉乘结果,为0向量则两向量平行,否则垂直于两向量
|
行列式的结果
|
||||||
"""
|
"""
|
||||||
return Vector3(self.y * other.z - self.z * other.y,
|
return Vector3(self.y * other.z - self.z * other.y,
|
||||||
self.z * other.x - self.x * other.z,
|
self.z * other.x - self.x * other.z,
|
||||||
self.x * other.y - self.y * other.x)
|
self.x * other.y - self.y * other.x)
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
"""
|
||||||
|
将向量归一化。
|
||||||
|
|
||||||
|
自体归一化,不返回值。
|
||||||
|
"""
|
||||||
|
length = self.length
|
||||||
|
self.x /= length
|
||||||
|
self.y /= length
|
||||||
|
self.z /= length
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def length(self) -> float:
|
def length(self) -> float:
|
||||||
"""
|
"""
|
||||||
@ -117,7 +138,7 @@ class Vector3:
|
|||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __sub__(self, other: 'Point3') -> 'Point3':
|
def __sub__(self, other: 'Point3') -> "Point3":
|
||||||
...
|
...
|
||||||
|
|
||||||
def __sub__(self, other):
|
def __sub__(self, other):
|
||||||
@ -133,9 +154,9 @@ class Vector3:
|
|||||||
elif isinstance(other, Point3):
|
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:
|
else:
|
||||||
raise TypeError(f"unsupported operand type(s) for -: 'Vector3' and '{type(other)}'")
|
raise TypeError(f"unsupported operand type(s) for -: \"Vector3\" and \"{type(other)}\"")
|
||||||
|
|
||||||
def __rsub__(self, other: Point3):
|
def __rsub__(self, other: 'Point3'):
|
||||||
"""
|
"""
|
||||||
P - V -> P
|
P - V -> P
|
||||||
Args:
|
Args:
|
||||||
@ -150,54 +171,41 @@ class Vector3:
|
|||||||
raise TypeError(f"unsupported operand type(s) for -: '{type(other)}' and 'Vector3'")
|
raise TypeError(f"unsupported operand type(s) for -: '{type(other)}' and 'Vector3'")
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __mul__(self, other: float) -> 'Vector3':
|
def __mul__(self, other: RealNumber) -> 'Vector3':
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __mul__(self, other: 'Vector3') -> float:
|
def __mul__(self, other: 'Vector3') -> 'Vector3':
|
||||||
...
|
...
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other: 'RealNumber | Vector3') -> 'Vector3':
|
||||||
"""
|
"""
|
||||||
点乘法。包括点乘和数乘。
|
数组运算 非点乘。点乘使用@,叉乘使用cross。
|
||||||
V * V -> float\n
|
|
||||||
Args:
|
Args:
|
||||||
other:
|
other:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float
|
|
||||||
Raises:
|
|
||||||
TypeError: 不支持的类型
|
|
||||||
"""
|
"""
|
||||||
if isinstance(other, (int, float)):
|
if isinstance(other, RealNumber):
|
||||||
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):
|
elif isinstance(other, Vector3):
|
||||||
return 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)
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"unsupported operand type(s) for *: 'Vector3' and '{type(other)}'")
|
raise TypeError(f"unsupported operand type(s) for *: 'Vector3' and '{type(other)}'")
|
||||||
|
|
||||||
def __rmul__(self, other: float) -> 'Vector3':
|
def __rmul__(self, other: RealNumber) -> 'Vector3':
|
||||||
"""
|
|
||||||
右乘。
|
|
||||||
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':
|
def __matmul__(self, other: 'Vector3') -> float:
|
||||||
"""
|
"""
|
||||||
向量积 叉乘:V1 @ V2 -> V3
|
点乘。
|
||||||
Args:
|
Args:
|
||||||
other:
|
other:
|
||||||
Returns:
|
Returns:
|
||||||
叉乘结果,为0向量则两向量平行,否则垂直于两向量
|
|
||||||
"""
|
"""
|
||||||
return Vector3(self.y * other.z - self.z * other.y,
|
return self.x * other.x + self.y * other.y + self.z * other.z
|
||||||
self.z * other.x - self.x * other.z,
|
|
||||||
self.x * other.y - self.y * other.x)
|
|
||||||
|
|
||||||
def __truediv__(self, other: float) -> 'Vector3':
|
def __truediv__(self, other: RealNumber) -> '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):
|
def __neg__(self):
|
||||||
@ -208,3 +216,16 @@ class Vector3:
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Vector3({self.x}, {self.y}, {self.z})"
|
return f"Vector3({self.x}, {self.y}, {self.z})"
|
||||||
|
|
||||||
|
|
||||||
|
zero_vector3 = Vector3(0, 0, 0)
|
||||||
|
"""零向量"""
|
||||||
|
x_axis = Vector3(1, 0, 0)
|
||||||
|
"""x轴单位向量"""
|
||||||
|
y_axis = Vector3(0, 1, 0)
|
||||||
|
"""y轴单位向量"""
|
||||||
|
z_axis = Vector3(0, 0, 1)
|
||||||
|
"""z轴单位向量"""
|
||||||
|
|
||||||
|
v1: Vector3 = Vector3(1, 2, 3) * 3.0
|
||||||
|
v2: Vector3 = 3.0 * Vector3(1, 2, 3)
|
||||||
|
23
pyproject.toml
Normal file
23
pyproject.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[project]
|
||||||
|
name = "mbcp"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A tool for Minecraft particle production"
|
||||||
|
authors = [
|
||||||
|
{name = "snowykami", email = "snowykami@outlook.com"},
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"pytest~=8.3.2",
|
||||||
|
"numpy~=2.0.1",
|
||||||
|
"liteyukibot>=6.3.9",
|
||||||
|
]
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
readme = "README.md"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["pdm-backend"]
|
||||||
|
build-backend = "pdm.backend"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.pdm]
|
||||||
|
distribution = true
|
@ -1,2 +0,0 @@
|
|||||||
pytest~=8.3.2
|
|
||||||
numpy~=2.0.1
|
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
32
tests/answer.py
Normal file
32
tests/answer.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||||
|
|
||||||
|
@Time : 2024/8/27 下午1:03
|
||||||
|
@Author : snowykami
|
||||||
|
@Email : snowykami@outlook.com
|
||||||
|
@File : .answer.py
|
||||||
|
@Software: PyCharm
|
||||||
|
"""
|
||||||
|
from liteyuki.log import logger # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def output_answer(correct_ans, actual_ans, question: str = None):
|
||||||
|
"""
|
||||||
|
输出答案
|
||||||
|
Args:
|
||||||
|
correct_ans:
|
||||||
|
actual_ans:
|
||||||
|
question:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
print("")
|
||||||
|
if question is not None:
|
||||||
|
logger.info(f"问题:{question}")
|
||||||
|
r = correct_ans == actual_ans
|
||||||
|
if r:
|
||||||
|
logger.success(f"测试正确 正确答案:{correct_ans} 实际答案:{actual_ans}")
|
||||||
|
else:
|
||||||
|
logger.error(f"测试错误 正确答案:{correct_ans} 实际答案:{actual_ans}")
|
@ -13,21 +13,8 @@ import logging
|
|||||||
from mbcp.mp_math.point import Point3
|
from mbcp.mp_math.point import Point3
|
||||||
from mbcp.mp_math.vector import Vector3
|
from mbcp.mp_math.vector import Vector3
|
||||||
from mbcp.mp_math.line import Line3
|
from mbcp.mp_math.line import Line3
|
||||||
|
from tests.answer import output_answer
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ import logging
|
|||||||
|
|
||||||
from mbcp.mp_math.line import Line3
|
from mbcp.mp_math.line import Line3
|
||||||
from mbcp.mp_math.plane import Plane3
|
from mbcp.mp_math.plane import Plane3
|
||||||
|
from mbcp.mp_math.point import Point3
|
||||||
|
from mbcp.mp_math.vector import Vector3
|
||||||
|
|
||||||
|
|
||||||
class TestPlane3:
|
class TestPlane3:
|
||||||
@ -20,10 +22,10 @@ class TestPlane3:
|
|||||||
"""
|
"""
|
||||||
测试平面的交线
|
测试平面的交线
|
||||||
"""
|
"""
|
||||||
correct_ans = Line3(4, 3, 1, 1)
|
correct_ans = Line3(Point3(-3, 2, 5), Vector3(4, 3, 1))
|
||||||
|
|
||||||
pl1 = Plane3(1, 0, -4, 23)
|
pl1 = Plane3(1, 0, -4, 23)
|
||||||
pl2 = Plane3(2, -1, -5, 33)
|
pl2 = Plane3(2, -1, -5, 33)
|
||||||
actual_ans = pl1.cal_intersection_line3(pl2)
|
actual_ans = pl1 & pl2 # 平面交线
|
||||||
logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}")
|
logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}")
|
||||||
assert actual_ans == correct_ans
|
assert actual_ans == correct_ans
|
||||||
|
@ -12,6 +12,8 @@ import logging
|
|||||||
|
|
||||||
from mbcp.mp_math.vector import Vector3
|
from mbcp.mp_math.vector import Vector3
|
||||||
|
|
||||||
|
from tests.answer import output_answer
|
||||||
|
|
||||||
|
|
||||||
class TestVector3:
|
class TestVector3:
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ class TestVector3:
|
|||||||
"""
|
"""
|
||||||
v1 = Vector3(1, 2, 3)
|
v1 = Vector3(1, 2, 3)
|
||||||
v2 = Vector3(3, 4, 5)
|
v2 = Vector3(3, 4, 5)
|
||||||
actual_ans = v1 @ v2
|
actual_ans = v1.cross(v2)
|
||||||
correct_ans = Vector3(-2, 4, -2)
|
correct_ans = Vector3(-2, 4, -2)
|
||||||
logging.info(f"正确答案{correct_ans} 实际答案{v1 @ v2}")
|
logging.info(f"正确答案{correct_ans} 实际答案{v1 @ v2}")
|
||||||
|
|
||||||
@ -34,18 +36,20 @@ class TestVector3:
|
|||||||
测试判断向量是否平行
|
测试判断向量是否平行
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
|
"""小题1"""
|
||||||
|
correct_ans = True
|
||||||
v1 = Vector3(1, 2, 3)
|
v1 = Vector3(1, 2, 3)
|
||||||
v2 = Vector3(3, 6, 9)
|
v2 = Vector3(3, 6, 9)
|
||||||
actual_ans = v1.is_parallel(v2)
|
actual_ans = v1.is_parallel(v2)
|
||||||
correct_ans = True
|
output_answer(correct_ans, actual_ans)
|
||||||
logging.info("v1和v2是否平行:%s", v1.is_parallel(v2))
|
|
||||||
assert correct_ans == actual_ans
|
assert correct_ans == actual_ans
|
||||||
|
|
||||||
|
"""小题2"""
|
||||||
|
correct_ans = False
|
||||||
v1 = Vector3(1, 2, 3)
|
v1 = Vector3(1, 2, 3)
|
||||||
v2 = Vector3(3, 6, 8)
|
v2 = Vector3(3, 6, 8)
|
||||||
actual_ans = v1.is_parallel(v2)
|
actual_ans = v1.is_parallel(v2)
|
||||||
correct_ans = False
|
output_answer(correct_ans, actual_ans)
|
||||||
logging.info("v1和v2是否平行:%s", v1.is_parallel(v2))
|
|
||||||
assert correct_ans == actual_ans
|
assert correct_ans == actual_ans
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
"""
|
"""
|
||||||
应用题测试集
|
应用题测试集
|
||||||
"""
|
"""
|
||||||
import logging
|
from liteyuki.log import logger # type: ignore
|
||||||
|
|
||||||
from mbcp.mp_math.line import Line3
|
from mbcp.mp_math.line import Line3
|
||||||
from mbcp.mp_math.plane import Plane3
|
from mbcp.mp_math.plane import Plane3
|
||||||
from mbcp.mp_math.point import Point3
|
from mbcp.mp_math.point import Point3
|
||||||
|
from mbcp.mp_math.vector import Vector3
|
||||||
|
from .answer import output_answer
|
||||||
|
|
||||||
|
|
||||||
class TestWordProblem:
|
class TestWordProblem:
|
||||||
@ -16,17 +17,17 @@ class TestWordProblem:
|
|||||||
同济大学《高等数学》第八版 下册 第八章第四节例4
|
同济大学《高等数学》第八版 下册 第八章第四节例4
|
||||||
问题:求与两平面x-4z-3=0和2x-y-5z-1=0的交线平行且过点(-3, 2, 5)的直线方程。
|
问题:求与两平面x-4z-3=0和2x-y-5z-1=0的交线平行且过点(-3, 2, 5)的直线方程。
|
||||||
"""
|
"""
|
||||||
correct_ans = Line3(4, 3, 1, 1)
|
question = "求与两平面x-4z-3=0和2x-y-5z-1=0的交线平行且过点(-3, 2, 5)的直线方程。"
|
||||||
|
correct_ans = Line3(Point3(-3, 2, 5), Vector3(4, 3, 1))
|
||||||
pl1 = Plane3(1, 0, -4, -3)
|
pl1 = Plane3(1, 0, -4, -3)
|
||||||
pl2 = Plane3(2, -1, -5, -1)
|
pl2 = Plane3(2, -1, -5, -1)
|
||||||
p = Point3(-3, 2, 5)
|
p = Point3(-3, 2, 5)
|
||||||
"""解法1"""
|
"""解法1"""
|
||||||
# 求直线方向向量s
|
# 求直线方向向量s
|
||||||
s = pl1.normal @ pl2.normal
|
s = pl1.normal.cross(pl2.normal)
|
||||||
actual_ans = Line3.from_point_and_direction(p, s)
|
actual_ans = Line3(p, s)
|
||||||
|
|
||||||
logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}")
|
output_answer(correct_ans, actual_ans, question)
|
||||||
assert actual_ans == correct_ans
|
assert actual_ans == correct_ans
|
||||||
|
|
||||||
"""解法2"""
|
"""解法2"""
|
||||||
@ -36,8 +37,40 @@ class TestWordProblem:
|
|||||||
pl4 = pl2.cal_parallel_plane3(p)
|
pl4 = pl2.cal_parallel_plane3(p)
|
||||||
# 求pl3和pl4的交线
|
# 求pl3和pl4的交线
|
||||||
actual_ans = pl3.cal_intersection_line3(pl4)
|
actual_ans = pl3.cal_intersection_line3(pl4)
|
||||||
print(pl3, pl4, actual_ans)
|
|
||||||
|
|
||||||
logging.info(f"正确答案:{correct_ans} 实际答案:{actual_ans}")
|
output_answer(correct_ans, actual_ans, question)
|
||||||
assert actual_ans == correct_ans
|
assert actual_ans == correct_ans
|
||||||
|
|
||||||
|
def test_c8s4e5(self):
|
||||||
|
"""
|
||||||
|
同济大学《高等数学》第八版 下册 第八章第四节例5
|
||||||
|
|
||||||
|
求直线(x-2)/1=(y-3)/1=(z-4)/2与平面2x+y+z-6=0的交点。
|
||||||
|
"""
|
||||||
|
question = "求直线(x-2)/1=(y-3)/1=(z-4)/2与平面2x+y+z-6=0的交点。"
|
||||||
|
"""正确答案"""
|
||||||
|
correct_ans = Point3(1, 2, 2)
|
||||||
|
"""题目已知量"""
|
||||||
|
line = Line3(Point3(2, 3, 4), Vector3(1, 1, 2))
|
||||||
|
plane = Plane3(2, 1, 1, -6)
|
||||||
|
|
||||||
|
"""解"""
|
||||||
|
actual_ans = plane & line
|
||||||
|
output_answer(correct_ans, actual_ans, question)
|
||||||
|
|
||||||
|
def test_c8s4e6(self):
|
||||||
|
question = "求过点(2, 3, 1)且与直线(x+1)/3 = (y-1)/2 = z/-1垂直相交的直线的方程。"
|
||||||
|
"""正确答案"""
|
||||||
|
correct_ans = Line3(Point3(2, 1, 3), Vector3(2, -1, 4))
|
||||||
|
"""题目已知量"""
|
||||||
|
point = Point3(2, 3, 1)
|
||||||
|
line = Line3(Point3(-1, 1, 0), Vector3(3, 2, -1))
|
||||||
|
|
||||||
|
"""解"""
|
||||||
|
# 先作平面过点且垂直与已知直线
|
||||||
|
pl = line.cal_perpendicular(point)
|
||||||
|
logger.debug(line.get_point(1))
|
||||||
|
|
||||||
|
# output_answer(correct_ans, actual_ans, question)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user