Coverage for tests \ test_orthonormal_basis_calculator.py: 100%
40 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-27 03:59 -0400
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-27 03:59 -0400
1# SPDX-FileCopyrightText: 2026-present Pavel M. Penev <pavpen@gmail.com>
2#
3# SPDX-License-Identifier: MIT
5import math
6from typing import cast
8import pytest
10from pavpen.geometry_calculator.faults import CalculationPrecisionExceededError
11from pavpen.geometry_calculator.orthonormal_basis_calculator import OrthonormalBasisCalculator
12from pavpen.geometry_calculator.vector_field_implementations.tuple_float_vector_field_operations import (
13 TupleFloatVectorFieldOperations,
14)
17class TestOrthonormalBasisCalculator:
18 def test_constructor_throws_on_empty_points_collection(self):
19 # Setup
20 vector_field_operations = TupleFloatVectorFieldOperations.for_1d()
22 # Act, and verify
23 with pytest.raises(ValueError, match=r"The `points` argument must contain at least one element."):
24 OrthonormalBasisCalculator(
25 vector_field_operations=vector_field_operations,
26 float_tolerance=1e-8,
27 points=[],
28 )
30 def test_calculate_throws_on_empty_points_collection(self):
31 # Setup
32 vector_field_operations = TupleFloatVectorFieldOperations.for_1d()
33 points = [(3.0,)]
34 calculator = OrthonormalBasisCalculator(
35 vector_field_operations=vector_field_operations,
36 float_tolerance=1e-8,
37 points=points,
38 )
40 # Act, and verify
41 points.clear()
42 with pytest.raises(
43 ValueError,
44 match=r"The `points` collection in OrthonormalBasisCalculator must contain at least one element.",
45 ):
46 calculator.calculate()
48 def test_calculate_throws_on_redundant_points(self):
49 # Setup
50 vector_field_operations = TupleFloatVectorFieldOperations.for_3d()
51 calculator = OrthonormalBasisCalculator(
52 vector_field_operations=vector_field_operations,
53 float_tolerance=1e-8,
54 points=[(0, 0, 0), (0, 0, -1), (0, 0, 1)],
55 )
57 # Act, and verify
58 with pytest.raises(CalculationPrecisionExceededError) as exc_info:
59 calculator.calculate(require_points_are_non_redundant=True)
61 # Verify
62 exception = cast("CalculationPrecisionExceededError", exc_info.value)
63 assert exception.tolerance == pytest.approx(1e-8)
64 assert exception.calculation_error == float("inf")
65 assert exception.value_name == "non_spanned_component_norm"
67 def test_calculate_ignores_redundant_points(self):
68 # Setup
69 vector_field_operations = TupleFloatVectorFieldOperations.for_3d()
70 calculator = OrthonormalBasisCalculator(
71 vector_field_operations=vector_field_operations,
72 float_tolerance=1e-8,
73 points=[(0, 0, 0), (0, 0, -1), (0, 0, 1)],
74 )
76 # Act
77 basis_vectors = calculator.calculate(require_points_are_non_redundant=False).basis_vectors
79 # Verify
80 assert len(basis_vectors) == 1
81 assert basis_vectors[0] == pytest.approx((0, 0, -1))
83 def test_calculate_returns_an_orthonormal_basis(self):
84 # Setup
85 vector_field_operations = TupleFloatVectorFieldOperations.for_3d()
86 calculator = OrthonormalBasisCalculator(
87 vector_field_operations=vector_field_operations,
88 float_tolerance=1e-8,
89 points=[(1, 1, 1), (1, 1, -1), (0, 0, 0)],
90 )
92 # Act
93 basis_vectors = calculator.calculate(require_points_are_non_redundant=True).basis_vectors
95 # Verify
96 assert len(basis_vectors) == 2
97 assert basis_vectors[0] == pytest.approx((0, 0, -1))
98 assert basis_vectors[1] == pytest.approx((-1.0 / math.sqrt(2), -1.0 / math.sqrt(2), 0))