diff --git a/mbcp/mp_math/angle.py b/mbcp/mp_math/angle.py index f339c06..6240b96 100644 --- a/mbcp/mp_math/angle.py +++ b/mbcp/mp_math/angle.py @@ -8,6 +8,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @File : angle.py @Software: PyCharm """ +import math from typing import overload from .const import PI # type: ignore @@ -71,6 +72,60 @@ class AnyAngle: """ return AnyAngle(-self.radian % (2 * PI), is_radian=True) + @property + def sin(self) -> float: + """ + 正弦值。 + Returns: + 正弦值 + """ + return math.sin(self.radian) + + @property + def cos(self) -> float: + """ + 余弦值。 + Returns: + 余弦值 + """ + return math.cos(self.radian) + + @property + def tan(self) -> float: + """ + 正切值。 + Returns: + 正切值 + """ + return math.tan(self.radian) + + @property + def cot(self) -> float: + """ + 余切值。 + Returns: + 余切值 + """ + return 1 / math.tan(self.radian) + + @property + def sec(self) -> float: + """ + 正割值。 + Returns: + 正割值 + """ + return 1 / math.cos(self.radian) + + @property + def csc(self) -> float: + """ + 余割值。 + Returns: + 余割值 + """ + return 1 / math.sin(self.radian) + def __add__(self, other: 'AnyAngle') -> 'AnyAngle': return AnyAngle(self.radian + other.radian, is_radian=True) diff --git a/mbcp/mp_math/const.py b/mbcp/mp_math/const.py index d7bc4b9..ac54de6 100644 --- a/mbcp/mp_math/const.py +++ b/mbcp/mp_math/const.py @@ -16,4 +16,6 @@ E = math.e GOLDEN_RATIO = (1 + math.sqrt(5)) / 2 GAMMA = 0.57721566490153286060651209008240243104215933593992 EPSILON = 0.0001 - +"""ε""" +APPROX = 0.001 +"""约等于误差""" diff --git a/mbcp/mp_math/line.py b/mbcp/mp_math/line.py index 93945ac..9fdde2d 100644 --- a/mbcp/mp_math/line.py +++ b/mbcp/mp_math/line.py @@ -8,8 +8,10 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @File : other.py @Software: PyCharm """ +import math from typing import TYPE_CHECKING +from .const import APPROX from .mp_math_typing import OneSingleVarFunc, RealNumber from .utils import sign_format from .vector import Vector3 @@ -30,6 +32,17 @@ class Line3: self.point = point self.direction = direction + def approx(self, other: 'Line3', epsilon: float = APPROX) -> bool: + """ + 判断两条直线是否近似相等。 + Args: + other: 另一条直线 + epsilon: 误差 + Returns: + 是否近似相等 + """ + return self.is_approx_parallel(other, epsilon) and (self.point - other.point).is_approx_parallel(self.direction, epsilon) + def cal_angle(self, other: 'Line3') -> 'AnyAngle': """ 计算直线和直线之间的夹角。 @@ -42,6 +55,28 @@ class Line3: """ return self.direction.cal_angle(other.direction) + def cal_distance(self, other: 'Line3 | Point3') -> float: + """ + 计算直线和直线或点之间的距离。 + Args: + other: 平行直线或点 + + Returns: + 距离 + Raises: + ValueError: 直线不平行 + TypeError: 不支持的类型 + """ + if isinstance(other, Line3): + if self.is_parallel(other): + return (self.point - other.point).cross(self.direction).length / self.direction.length + else: + raise ValueError("Lines are not parallel.") + elif isinstance(other, Point3): + return (other - self.point).cross(self.direction).length / self.direction.length + else: + raise TypeError("Unsupported type.") + def cal_intersection(self, other: 'Line3') -> 'Point3': """ 计算两条直线的交点。 @@ -49,12 +84,16 @@ class Line3: other: 另一条直线 Returns: 交点 + Raises: + ValueError: 直线平行 + ValueError: 直线不共面 """ if self.is_parallel(other): raise ValueError("Lines are parallel and do not intersect.") if not self.is_coplanar(other): raise ValueError("Lines are not coplanar and do not intersect.") - return self.point + self.direction.cross(other.direction) + return (self.point + (self.direction.cross(other.direction) @ other.direction.cross(self.point - other.point)) / + self.direction.cross(other.direction).length ** 2 * self.direction) def cal_perpendicular(self, point: 'Point3') -> 'Line3': """ @@ -86,6 +125,17 @@ class Line3: lambda t: self.point.y + self.direction.y * t, lambda t: self.point.z + self.direction.z * t) + def is_approx_parallel(self, other: 'Line3', epsilon: float = 1e-6) -> bool: + """ + 判断两条直线是否近似平行。 + Args: + other: 另一条直线 + epsilon: 误差 + Returns: + 是否近似平行 + """ + return self.direction.is_approx_parallel(other.direction, epsilon) + def is_parallel(self, other: 'Line3') -> bool: """ 判断两条直线是否平行。 @@ -106,15 +156,26 @@ class Line3: """ return self.is_parallel(other) and (self.point - other.point).is_parallel(self.direction) + def is_point_on(self, point: 'Point3') -> bool: + """ + 判断点是否在直线上。 + Args: + point: 点 + Returns: + 是否在直线上 + """ + return (point - self.point).is_parallel(self.direction) + def is_coplanar(self, other: 'Line3') -> bool: """ 判断两条直线是否共面。 + 充要条件:两直线方向向量的叉乘与两直线上任意一点的向量的点积为0。 Args: other: 另一条直线 Returns: 是否共面 """ - return self.direction.cross(other.direction).is_parallel(self.direction) + return self.direction.cross(other.direction) @ (self.point - other.point) == 0 def simplify(self): """ @@ -147,15 +208,20 @@ class Line3: direction = p2 - p1 return cls(p1, direction) - def __and__(self, other: 'Line3') -> 'Point3': + def __and__(self, other: 'Line3') -> 'Line3 | Point3 | None': """ - 计算两条直线点集合的交集。交点 + 计算两条直线点集合的交集。重合线返回自身,平行线返回None,交线返回交点。 Args: other: 另一条直线 Returns: 交点 """ - return self.cal_intersection(other) + if self.is_collinear(other): + return self + elif self.is_parallel(other) or not self.is_coplanar(other): + return None + else: + return self.cal_intersection(other) def __eq__(self, other) -> bool: """ diff --git a/mbcp/mp_math/plane.py b/mbcp/mp_math/plane.py index a979e62..e87deff 100644 --- a/mbcp/mp_math/plane.py +++ b/mbcp/mp_math/plane.py @@ -7,10 +7,11 @@ from typing import TYPE_CHECKING, overload import numpy as np +from .const import APPROX from .vector import Vector3, zero_vector3 from .line import Line3 from .point import Point3 -from .utils import sign +from .utils import approx, sign if TYPE_CHECKING: from .angle import AnyAngle @@ -31,6 +32,28 @@ class Plane3: self.c = c self.d = d + def approx(self, other: 'Plane3', epsilon: float = APPROX) -> bool: + """ + 判断两个平面是否近似相等。 + Args: + other: + epsilon: + + Returns: + 是否近似相等 + """ + if self.a != 0: + k = other.a / self.a + return approx(other.b, self.b * k) and approx(other.c, self.c * k) and approx(other.d, self.d * k) + elif self.b != 0: + k = other.b / self.b + return approx(other.a, self.a * k) and approx(other.c, self.c * k) and approx(other.d, self.d * k) + elif self.c != 0: + k = other.c / self.c + return approx(other.a, self.a * k) and approx(other.b, self.b * k) and approx(other.d, self.d * k) + else: + return False + def cal_angle(self, other: 'Line3 | Plane3') -> 'AnyAngle': """ 计算平面与平面之间的夹角。 @@ -126,6 +149,16 @@ class Plane3: """ return Plane3.from_point_and_normal(point, self.normal) + def is_parallel(self, other: 'Plane3') -> bool: + """ + 判断两个平面是否平行。 + Args: + other: 另一个平面 + Returns: + 是否平行 + """ + return self.normal.is_parallel(other.normal) + @property def normal(self) -> 'Vector3': """ @@ -237,5 +270,10 @@ class Plane3: else: raise TypeError(f"unsupported operand type(s) for &: 'Plane3' and '{type(other)}'") + def __eq__(self, other) -> bool: + return self.approx(other) + def __rand__(self, other: 'Line3') -> 'Point3': return self.cal_intersection_point3(other) + + diff --git a/mbcp/mp_math/point.py b/mbcp/mp_math/point.py index ff6ce9f..7a1bed6 100644 --- a/mbcp/mp_math/point.py +++ b/mbcp/mp_math/point.py @@ -1,5 +1,8 @@ from typing import TYPE_CHECKING, overload +from .const import APPROX +from .utils import approx + if TYPE_CHECKING: from .vector import Vector3 # type: ignore @@ -17,6 +20,18 @@ class Point3: self.y = y self.z = z + def approx(self, other: "Point3", epsilon: float = APPROX) -> bool: + """ + 判断两个点是否近似相等。 + Args: + other: + epsilon: + + Returns: + 是否近似相等 + """ + return all([abs(self.x - other.x) < epsilon, abs(self.y - other.y) < epsilon, abs(self.z - other.z) < epsilon]) + def __str__(self): return f"Point3({self.x}, {self.y}, {self.z})" @@ -45,7 +60,7 @@ class Point3: other: Returns: """ - return self.x == other.x and self.y == other.y and self.z == other.z + return approx(self.x, other.x) and approx(self.y, other.y) and approx(self.z, other.z) def __sub__(self, other: "Point3") -> "Vector3": """ diff --git a/mbcp/mp_math/segment.py b/mbcp/mp_math/segment.py index d4d3afa..4168445 100644 --- a/mbcp/mp_math/segment.py +++ b/mbcp/mp_math/segment.py @@ -23,68 +23,18 @@ class Segment3: :param p1: :param p2: """ - self._start = p1 - self._end = p2 + self.p1 = p1 + self.p2 = p2 """方向向量""" - self._direction = self._end - self._start + self.direction = self.p2 - self.p1 """长度""" - self._length = self._direction.length + self.length = self.direction.length """中心点""" - self._midpoint = (self._start + self._end) / 2 + self.midpoint = Point3((self.p1.x + self.p2.x) / 2, (self.p1.y + self.p2.y) / 2, (self.p1.z + self.p2.z) / 2) + + def __repr__(self): + return f"Segment3({self.p1}, {self.p2})" def __str__(self): - return f"Segment3({self._start}, {self._end})" - - def _unset_properties(self): - self._length = None - self._direction = None - self._midpoint = None - - @property - def start(self) -> "Point3": - return self._start - - @start.setter - def start(self, value: "Point3"): - self._start = value - self._unset_properties() - - @property - def end(self) -> "Point3": - return self._end - - @end.setter - def end(self, value: "Point3"): - self._end = value - self._unset_properties() - - @property - def length(self) -> float: - """ - 线段的长度。 - :return: - """ - if self._length is None: - self._length = (self._end - self._start).length - return self._length - - @property - def direction(self) -> "Vector3": - """ - 线段的方向向量。 - :return: - """ - if self._direction is None: - self._direction = self._end - self._start - return self._direction - - @property - def midpoint(self) -> "Point3": - """ - 线段的中点。 - :return: - """ - if self._midpoint is None: - self._midpoint = (self._start + self._end) / 2 - return self._midpoint + return f"Segment3({self.p1} -> {self.p2})" diff --git a/mbcp/mp_math/utils.py b/mbcp/mp_math/utils.py index ef5c072..ae9f85a 100644 --- a/mbcp/mp_math/utils.py +++ b/mbcp/mp_math/utils.py @@ -8,9 +8,16 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @File : utils.py @Software: PyCharm """ -from typing import overload +from typing import overload, TYPE_CHECKING -from mbcp.mp_math.mp_math_typing import RealNumber +from .mp_math_typing import RealNumber +from .const import APPROX + +if TYPE_CHECKING: + from .vector import Vector3 + from .point import Point3 + from .plane import Plane3 + from .line import Line3 def clamp(x: float, min_: float, max_: float) -> float: @@ -27,24 +34,36 @@ def clamp(x: float, min_: float, max_: float) -> float: return max(min(x, max_), min_) -class Approx(float): +class Approx: """ - 用于近似比较浮点数的类。 - """ - epsilon = 0.001 - """全局近似值。""" + 用于近似比较对象 - def __new__(cls, x: RealNumber): - return super().__new__(cls, x) + 已实现对象 实数 Vector3 Point3 Plane3 Line3 + """ + + def __init__(self, value: RealNumber): + self.value = value def __eq__(self, other): - return abs(self - other) < Approx.epsilon + if isinstance(self.value, (float, int)): + if isinstance(other, (float, int)): + return abs(self.value - other) < APPROX + else: + self.raise_type_error(other) + elif isinstance(self.value, Vector3): + if isinstance(other, (Vector3, Point3, Plane3, Line3)): + return all([approx(self.value.x, other.x), approx(self.value.y, other.y), approx(self.value.z, other.z)]) + else: + self.raise_type_error(other) + + def raise_type_error(self, other): + raise TypeError(f"Unsupported type: {type(self.value)} and {type(other)}") def __ne__(self, other): return not self.__eq__(other) -def approx(x: float, y: float = 0.0, epsilon: float = 0.0001) -> bool: +def approx(x: float, y: float = 0.0, epsilon: float = APPROX) -> bool: """ 判断两个数是否近似相等。或包装一个实数,用于判断是否近似于0。 Args: diff --git a/mbcp/mp_math/vector.py b/mbcp/mp_math/vector.py index 6988472..7e1c168 100644 --- a/mbcp/mp_math/vector.py +++ b/mbcp/mp_math/vector.py @@ -1,8 +1,12 @@ import math from typing import overload, TYPE_CHECKING +import numpy as np + +from .const import APPROX from .mp_math_typing import RealNumber from .point import Point3 +from .utils import approx if TYPE_CHECKING: from .angle import AnyAngle @@ -21,6 +25,18 @@ class Vector3: self.y = y self.z = z + def approx(self, other: 'Vector3', epsilon: float = APPROX) -> bool: + """ + 判断两个向量是否近似相等。 + Args: + other: + epsilon: + + Returns: + 是否近似相等 + """ + return all([abs(self.x - other.x) < epsilon, abs(self.y - other.y) < epsilon, abs(self.z - other.z) < epsilon]) + def cal_angle(self, other: 'Vector3') -> 'AnyAngle': """ 计算两个向量之间的夹角。 @@ -31,16 +47,6 @@ class Vector3: """ return AnyAngle(math.acos(self @ other / (self.length * other.length)), is_radian=True) - def is_parallel(self, other: 'Vector3') -> bool: - """ - 判断两个向量是否平行。 - Args: - other: 另一个向量 - Returns: - 是否平行 - """ - return self.cross(other) == Vector3(0, 0, 0) - def cross(self, other: 'Vector3') -> 'Vector3': """ 向量积 叉乘:v1 cross v2 -> v3 @@ -61,6 +67,27 @@ class Vector3: self.z * other.x - self.x * other.z, self.x * other.y - self.y * other.x) + def is_approx_parallel(self, other: 'Vector3', epsilon: float = APPROX) -> bool: + """ + 判断两个向量是否近似平行。 + Args: + other: 另一个向量 + epsilon: 允许的误差 + Returns: + 是否近似平行 + """ + return self.cross(other).length < epsilon + + def is_parallel(self, other: 'Vector3') -> bool: + """ + 判断两个向量是否平行。 + Args: + other: 另一个向量 + Returns: + 是否平行 + """ + return self.cross(other).approx(zero_vector3) + def normalize(self): """ 将向量归一化。 @@ -72,6 +99,15 @@ class Vector3: self.y /= length self.z /= length + @property + def np_array(self) -> 'np.ndarray': + """ + 返回numpy数组 + Returns: + """ + + return np.array([self.x, self.y, self.z]) + @property def length(self) -> float: """ @@ -90,6 +126,9 @@ class Vector3: """ return self / self.length + def __abs__(self): + return self.length + @overload def __add__(self, other: 'Vector3') -> 'Vector3': ... @@ -122,7 +161,7 @@ class Vector3: Returns: 是否相等 """ - return self.x == other.x and self.y == other.y and self.z == other.z + return approx(self.x, other.x) and approx(self.y, other.y) and approx(self.z, other.z) def __radd__(self, other: 'Point3') -> 'Point3': """ @@ -197,7 +236,7 @@ class Vector3: def __rmul__(self, other: 'RealNumber') -> 'Vector3': return self.__mul__(other) - def __matmul__(self, other: 'Vector3') -> float: + def __matmul__(self, other: 'Vector3') -> 'RealNumber': """ 点乘。 Args: diff --git a/tests/answer.py b/tests/answer.py index 4d67b07..0c2f0da 100644 --- a/tests/answer.py +++ b/tests/answer.py @@ -8,16 +8,19 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @File : .answer.py @Software: PyCharm """ +from typing import Optional + from liteyuki.log import logger # type: ignore -def output_answer(correct_ans, actual_ans, question: str = None): +def output_ans(correct_ans, actual_ans, condition: Optional[bool] = None,question: Optional[str] = None): """ 输出答案 Args: correct_ans: actual_ans: - question: + condition: 判对条件 + question: 问题 Returns: @@ -25,8 +28,31 @@ def output_answer(correct_ans, actual_ans, question: str = None): print("") if question is not None: logger.info(f"问题:{question}") - r = correct_ans == actual_ans + r = (correct_ans == actual_ans) if condition is None else condition if r: logger.success(f"测试正确 正确答案:{correct_ans} 实际答案:{actual_ans}") else: logger.error(f"测试错误 正确答案:{correct_ans} 实际答案:{actual_ans}") + assert r + + +def output_step_ans(correct_ans, actual_ans, condition: Optional[bool] = None, question: Optional[str] = None): + """ + 输出步骤答案 + Args: + correct_ans: 正确答案 + actual_ans: 实际答案 + condition: 判对条件 + question: 问题 + + Returns: + + """ + print("") + if question is not None: + logger.info(f" 步骤:{question}") + r = (correct_ans == actual_ans) if condition is None else condition + if r: + logger.success(f" 正确 正确:{correct_ans} 实际:{actual_ans}") + else: + logger.error(f" 错误 正确:{correct_ans} 实际:{actual_ans}") diff --git a/tests/test_line3.py b/tests/test_line3.py index 7388e5f..26516c0 100644 --- a/tests/test_line3.py +++ b/tests/test_line3.py @@ -13,8 +13,50 @@ import logging from mbcp.mp_math.point import Point3 from mbcp.mp_math.vector import Vector3 from mbcp.mp_math.line import Line3 -from tests.answer import output_answer +from tests.answer import output_ans +class TestLine3: + def test_equal(self): + line1 = Line3(Point3(1, 1, 1), Vector3(1, 1, 1)) + line2 = Line3(Point3(1, 1, 1), Vector3(2, 2, 2)) + output_ans(True, line1 == line2, question="判断两条直线是否相等") + # 反例 + line1 = Line3(Point3(1, 1, 1), Vector3(1, 1, 1)) + line2 = Line3(Point3(1, 1, 1), Vector3(2, 2, 2.000000001)) + output_ans(False, line1 == line2, question="判断两条直线是否不相等") + + def test_approx(self): + line1 = Line3(Point3(1, 1, 1), Vector3(1, 1, 1)) + line2 = Line3(Point3(1, 1, 1), Vector3(2, 2, 2.000000001)) + output_ans(True, line1.approx(line2), question="判断两条直线是否近似相等") + + # 反例 + line1 = Line3(Point3(1, 1, 1), Vector3(1, 1, 1)) + line2 = Line3(Point3(1, 1, 1), Vector3(2, 2, 3.1)) + output_ans(False, line1.approx(line2), question="判断两条直线是否不近似相等") + + def test_cal_intersection(self): + line1 = Line3.from_two_points(Point3(0, 0, 0), Point3(2, 2, 2)) + line2 = Line3.from_two_points(Point3(0, 0, 2), Point3(2, 2, 0)) + output_ans(Point3(1, 1, 1), line1 & line2, question="计算两条直线的交点测1") + + line1 = Line3.from_two_points(Point3(0, 0, 0), Point3(0, 2, 2)) + line2 = Line3.from_two_points(Point3(0, 0, 2), Point3(0, 2, 0)) + output_ans(Point3(0, 1, 1), line1 & line2, question="计算两条直线的交点测2") + + line1 = Line3.from_two_points(Point3(0, 0, 0), Point3(0, 0, 2)) + line2 = Line3.from_two_points(Point3(0, 0, 2), Point3(0, 2, 0)) + output_ans(Point3(0, 0, 2), line1 & line2, question="计算两条直线的交点测3") + + # 反例:平行线无交点 + line1 = Line3(Point3(1, 1, 1), Vector3(1, 1, 1)) + line2 = Line3(Point3(2, 3, 1), Vector3(1, 1, 1)) + output_ans(None, line1 & line2, question="平行线交集为空集") + + # 反例:重合线交集为自身 + line1 = Line3(Point3(1, 1, 1), Vector3(1, 1, 1)) + line2 = Line3(Point3(0, 0, 0), Vector3(2, 2, 2)) + output_ans(line1, line1 & line2, question="重合线的交集为自身") diff --git a/tests/test_vector3.py b/tests/test_vector3.py index 66acead..a4abd6e 100644 --- a/tests/test_vector3.py +++ b/tests/test_vector3.py @@ -12,7 +12,7 @@ import logging from mbcp.mp_math.vector import Vector3 -from tests.answer import output_answer +from tests.answer import output_ans class TestVector3: @@ -41,7 +41,7 @@ class TestVector3: v1 = Vector3(1, 2, 3) v2 = Vector3(3, 6, 9) actual_ans = v1.is_parallel(v2) - output_answer(correct_ans, actual_ans) + output_ans(correct_ans, actual_ans) assert correct_ans == actual_ans """小题2""" @@ -49,7 +49,7 @@ class TestVector3: v1 = Vector3(1, 2, 3) v2 = Vector3(3, 6, 8) actual_ans = v1.is_parallel(v2) - output_answer(correct_ans, actual_ans) + output_ans(correct_ans, actual_ans) assert correct_ans == actual_ans diff --git a/tests/test_word_problem.py b/tests/test_word_problem.py index 56d5ad2..c86dc7e 100644 --- a/tests/test_word_problem.py +++ b/tests/test_word_problem.py @@ -7,7 +7,7 @@ from mbcp.mp_math.line import Line3 from mbcp.mp_math.plane import Plane3 from mbcp.mp_math.point import Point3 from mbcp.mp_math.vector import Vector3 -from .answer import output_answer +from .answer import output_ans, output_step_ans class TestWordProblem: @@ -27,7 +27,7 @@ class TestWordProblem: s = pl1.normal.cross(pl2.normal) actual_ans = Line3(p, s) - output_answer(correct_ans, actual_ans, question) + output_ans(correct_ans, actual_ans, question=question) assert actual_ans == correct_ans """解法2""" @@ -38,7 +38,7 @@ class TestWordProblem: # 求pl3和pl4的交线 actual_ans = pl3.cal_intersection_line3(pl4) - output_answer(correct_ans, actual_ans, question) + output_ans(correct_ans, actual_ans, question=question) assert actual_ans == correct_ans def test_c8s4e5(self): @@ -56,21 +56,37 @@ class TestWordProblem: """解""" actual_ans = plane & line - output_answer(correct_ans, actual_ans, question) + output_ans(correct_ans, actual_ans, question=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) + point = Point3(2, 1, 3) line = Line3(Point3(-1, 1, 0), Vector3(3, 2, -1)) """解""" - # 先作平面过点且垂直与已知直线 - pl = line.cal_perpendicular(point) - logger.debug(line.get_point(1)) + # 先作过点且垂直与已知直线的平面 + s1_correct_ans = Plane3(3, 2, -1, -5) + pl = Plane3.from_point_and_normal(point, line.direction) - # output_answer(correct_ans, actual_ans, question) + output_step_ans(s1_correct_ans, pl, question="作过点且垂直与已知直线的平面") + # 求该平面与已知直线的交点 + s2_correct_ans = Point3(2 / 7, 13 / 7, -3 / 7) + s2_actual_ans = pl & line + output_step_ans(s2_correct_ans, s2_actual_ans, s1_correct_ans.approx(s1_correct_ans), question="求该平面与已知直线的交点") + + # 求所求直线的方向向量 + s3_correct_ans = (-6 / 7) * Vector3(2, -1, 4) + + dv = s2_correct_ans - point + + output_step_ans(s3_correct_ans, dv, condition=s3_correct_ans.unit.approx(dv.unit), question="求所求直线的方向向量") + + # 求所求直线的方程 + actual_ans = Line3(point, dv) + + output_ans(correct_ans, actual_ans, correct_ans.approx(actual_ans), question=question)