rbca 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rbca-0.1.0/.gitignore +45 -0
- rbca-0.1.0/.python-version +1 -0
- rbca-0.1.0/LICENSE +21 -0
- rbca-0.1.0/PKG-INFO +203 -0
- rbca-0.1.0/README.md +183 -0
- rbca-0.1.0/pyproject.toml +38 -0
- rbca-0.1.0/src/rbca/__init__.py +2 -0
- rbca-0.1.0/src/rbca/core.py +48 -0
- rbca-0.1.0/src/rbca/core_float.py +41 -0
- rbca-0.1.0/src/rbca/lib.py +146 -0
- rbca-0.1.0/src/rbca/lib_float.py +111 -0
- rbca-0.1.0/tests/test_core_api.py +132 -0
rbca-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# virtual environment
|
|
2
|
+
.venv/
|
|
3
|
+
.venv-test/
|
|
4
|
+
|
|
5
|
+
# Python cache
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
|
|
10
|
+
# build
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
archived_dist/
|
|
15
|
+
|
|
16
|
+
# test / coverage
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
|
|
21
|
+
# type checker / linter
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
.ruff_cache/
|
|
24
|
+
|
|
25
|
+
# Jupyter
|
|
26
|
+
.ipynb_checkpoints/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
|
|
32
|
+
# OS
|
|
33
|
+
.DS_Store
|
|
34
|
+
Thumbs.db
|
|
35
|
+
|
|
36
|
+
# environment variables
|
|
37
|
+
.env
|
|
38
|
+
.env.*
|
|
39
|
+
|
|
40
|
+
# logs
|
|
41
|
+
*.log
|
|
42
|
+
|
|
43
|
+
uv.lock
|
|
44
|
+
test.py
|
|
45
|
+
/manual_tests
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
rbca-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Imagination12357
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rbca-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rbca
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Recursive Binary Cosine Approximation
|
|
5
|
+
Project-URL: Homepage, https://github.com/Imagination12357/rbca
|
|
6
|
+
Project-URL: Repository, https://github.com/Imagination12357/rbca
|
|
7
|
+
Project-URL: Issues, https://github.com/Imagination12357/rbca/issues
|
|
8
|
+
Author: imagination12357
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: approximation,symbolic-math,sympy,trigonometry
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: sympy>=1.10.0
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# rbca
|
|
22
|
+
|
|
23
|
+
`rbca` (Recursive Binary Cosine Approximation) is a trigonometric approximation library with both symbolic and float APIs.
|
|
24
|
+
|
|
25
|
+
It computes `cos`, `sin`, and `tan` through recursive binary partitioning on angles.
|
|
26
|
+
|
|
27
|
+
The default APIs keep expressions in exact symbolic form (SymPy `Expr`) instead of forcing early floating-point evaluation. The `_float` APIs use float arithmetic internally for practical numeric calculation.
|
|
28
|
+
|
|
29
|
+
Current package version is `0.1.0`.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Symbolic trig outputs using SymPy expressions
|
|
34
|
+
- Practical float trig outputs using float arithmetic
|
|
35
|
+
- Recursive midpoint-based approximation for angle intervals
|
|
36
|
+
- Angle normalization over full `0~360` degree input range
|
|
37
|
+
- Three API families:
|
|
38
|
+
- `calc_*_pure`: mathematically pure symbolic recursion from `0` to `90`
|
|
39
|
+
- `calc_*`: symbolic recursion seeded with well-known anchor angles (`0, 15, 30, 45, 60, 75`)
|
|
40
|
+
- `calc_*_float`: practical float calculation using the same anchor strategy as `calc_*`
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
### Using `pip`
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install rbca
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Local development (this repository)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Project requirements:
|
|
57
|
+
- Python `>=3.10`
|
|
58
|
+
- `sympy>=1.10.0`
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from rbca import calc_cos, calc_cos_float, calc_sin, calc_sin_float, calc_tan
|
|
64
|
+
from sympy import sqrtdenest
|
|
65
|
+
|
|
66
|
+
depth = 5
|
|
67
|
+
|
|
68
|
+
cos_expr = calc_cos(37, depth) # SymPy Expr
|
|
69
|
+
sin_expr = calc_sin(37, depth) # SymPy Expr
|
|
70
|
+
tan_expr = calc_tan(37, depth) # SymPy Expr
|
|
71
|
+
|
|
72
|
+
cos_value = calc_cos_float(37, depth) # float
|
|
73
|
+
sin_value = calc_sin_float(37, depth) # float
|
|
74
|
+
|
|
75
|
+
print(cos_expr) # symbolic expression
|
|
76
|
+
print(float(cos_expr)) # numeric projection
|
|
77
|
+
print(sqrtdenest(cos_expr)) # optional radical denesting
|
|
78
|
+
print(cos_value) # practical float result
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Choosing an API Family
|
|
82
|
+
|
|
83
|
+
- Use `calc_cos_pure`, `calc_sin_pure`, and `calc_tan_pure` when you want the mathematically pure symbolic path.
|
|
84
|
+
- This path starts from `[0, 90]` only.
|
|
85
|
+
- Use `calc_cos`, `calc_sin`, and `calc_tan` when you want symbolic results with a more practical anchored start.
|
|
86
|
+
- This path uses anchor angles (`0, 15, 30, 45, 60, 75, 90`) to start from a narrower interval.
|
|
87
|
+
- Use `calc_cos_float`, `calc_sin_float`, and `calc_tan_float` when you want practical float results.
|
|
88
|
+
- This path is implemented with float arithmetic internally.
|
|
89
|
+
|
|
90
|
+
## Core API
|
|
91
|
+
|
|
92
|
+
- `calc_cos(angle, depth) -> Expr`
|
|
93
|
+
- `calc_sin(angle, depth) -> Expr`
|
|
94
|
+
- `calc_tan(angle, depth) -> Expr`
|
|
95
|
+
|
|
96
|
+
- `calc_cos_pure(angle, depth) -> Expr`
|
|
97
|
+
- `calc_sin_pure(angle, depth) -> Expr`
|
|
98
|
+
- `calc_tan_pure(angle, depth) -> Expr`
|
|
99
|
+
|
|
100
|
+
- `calc_cos_float(angle, depth) -> float`
|
|
101
|
+
- `calc_sin_float(angle, depth) -> float`
|
|
102
|
+
- `calc_tan_float(angle, depth) -> float`
|
|
103
|
+
|
|
104
|
+
For symbolic APIs, `angle` accepts `Rational | int | float | str` and is internally converted with `sympy.Rational` where needed.
|
|
105
|
+
|
|
106
|
+
Symbolic input note:
|
|
107
|
+
- `float` inputs are converted through `Rational(float_value)`, which can preserve binary-float artifacts.
|
|
108
|
+
- For exact decimal intent, prefer `str` or `Rational` directly.
|
|
109
|
+
- Example: use `"0.1"` instead of `0.1`.
|
|
110
|
+
|
|
111
|
+
For float APIs, `angle` accepts `int | float | str` and is internally converted with `float(angle)`.
|
|
112
|
+
|
|
113
|
+
`depth` is recursion depth:
|
|
114
|
+
- `depth = 0`: return one of the current interval endpoints
|
|
115
|
+
- larger depth: finer binary partitioning and generally tighter approximation
|
|
116
|
+
|
|
117
|
+
## How It Works
|
|
118
|
+
|
|
119
|
+
1. Normalize input angle to first quadrant (`0~90`) and track output sign.
|
|
120
|
+
2. Choose initial bracket interval:
|
|
121
|
+
- `calc_cos_pure`: `[0, 90]`
|
|
122
|
+
- `calc_cos`: narrower interval between predefined anchor angles when possible.
|
|
123
|
+
- `calc_cos_float`: same anchored interval strategy as `calc_cos`, but with float nodes and float values.
|
|
124
|
+
3. Recursively bisect the interval by midpoint angle.
|
|
125
|
+
4. Compute midpoint cosine via:
|
|
126
|
+
- a product/half-angle style expression using endpoint cosine values.
|
|
127
|
+
5. Recurse until `depth == 0` (or exact angle hit), then apply sign flip if needed.
|
|
128
|
+
|
|
129
|
+
## Notes and Limitations
|
|
130
|
+
|
|
131
|
+
- This library is symbolic-first; expressions can grow quickly as depth increases.
|
|
132
|
+
- For angles where cosine is zero (e.g. `90 + 180k` degrees), `calc_tan` and `calc_tan_pure` return `sympy.zoo`.
|
|
133
|
+
- For the same tangent singularities, `calc_tan_float` lets Python's `ZeroDivisionError` propagate.
|
|
134
|
+
- Inputs with symbolic free variables are rejected during normalization.
|
|
135
|
+
|
|
136
|
+
## Maths
|
|
137
|
+
Condition:
|
|
138
|
+
|
|
139
|
+
$$
|
|
140
|
+
0^\circ \le A,B \le 90^\circ
|
|
141
|
+
$$
|
|
142
|
+
|
|
143
|
+
Using the half-angle formula,
|
|
144
|
+
|
|
145
|
+
$$
|
|
146
|
+
\cos\frac{A+B}{2}
|
|
147
|
+
=
|
|
148
|
+
\sqrt{\frac{1+\cos(A+B)}{2}}
|
|
149
|
+
$$
|
|
150
|
+
|
|
151
|
+
Also, by the angle addition formula,
|
|
152
|
+
|
|
153
|
+
$$
|
|
154
|
+
\cos(A+B)
|
|
155
|
+
=
|
|
156
|
+
\cos A\cos B-\sin A\sin B
|
|
157
|
+
$$
|
|
158
|
+
|
|
159
|
+
Since
|
|
160
|
+
|
|
161
|
+
$$
|
|
162
|
+
0^\circ \le A,B \le 90^\circ
|
|
163
|
+
$$
|
|
164
|
+
|
|
165
|
+
we can use
|
|
166
|
+
|
|
167
|
+
$$
|
|
168
|
+
\sin A=\sqrt{1-\cos^2 A}
|
|
169
|
+
$$
|
|
170
|
+
|
|
171
|
+
and
|
|
172
|
+
|
|
173
|
+
$$
|
|
174
|
+
\sin B=\sqrt{1-\cos^2 B}
|
|
175
|
+
$$
|
|
176
|
+
|
|
177
|
+
Therefore,
|
|
178
|
+
|
|
179
|
+
$$
|
|
180
|
+
\cos(A+B)
|
|
181
|
+
=
|
|
182
|
+
\cos A\cos B
|
|
183
|
+
-
|
|
184
|
+
\sqrt{(1-\cos^2 A)(1-\cos^2 B)}
|
|
185
|
+
$$
|
|
186
|
+
|
|
187
|
+
Substituting this into the half-angle formula finally gives
|
|
188
|
+
|
|
189
|
+
$$
|
|
190
|
+
\boxed{
|
|
191
|
+
\cos\frac{A+B}{2}
|
|
192
|
+
=
|
|
193
|
+
\sqrt{
|
|
194
|
+
\frac{
|
|
195
|
+
1+\cos A\cos B-\sqrt{(1-\cos^2 A)(1-\cos^2 B)}
|
|
196
|
+
}{2}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
$$
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT License. See [`LICENSE`](./LICENSE).
|
rbca-0.1.0/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# rbca
|
|
2
|
+
|
|
3
|
+
`rbca` (Recursive Binary Cosine Approximation) is a trigonometric approximation library with both symbolic and float APIs.
|
|
4
|
+
|
|
5
|
+
It computes `cos`, `sin`, and `tan` through recursive binary partitioning on angles.
|
|
6
|
+
|
|
7
|
+
The default APIs keep expressions in exact symbolic form (SymPy `Expr`) instead of forcing early floating-point evaluation. The `_float` APIs use float arithmetic internally for practical numeric calculation.
|
|
8
|
+
|
|
9
|
+
Current package version is `0.1.0`.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Symbolic trig outputs using SymPy expressions
|
|
14
|
+
- Practical float trig outputs using float arithmetic
|
|
15
|
+
- Recursive midpoint-based approximation for angle intervals
|
|
16
|
+
- Angle normalization over full `0~360` degree input range
|
|
17
|
+
- Three API families:
|
|
18
|
+
- `calc_*_pure`: mathematically pure symbolic recursion from `0` to `90`
|
|
19
|
+
- `calc_*`: symbolic recursion seeded with well-known anchor angles (`0, 15, 30, 45, 60, 75`)
|
|
20
|
+
- `calc_*_float`: practical float calculation using the same anchor strategy as `calc_*`
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### Using `pip`
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install rbca
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Local development (this repository)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install -e .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Project requirements:
|
|
37
|
+
- Python `>=3.10`
|
|
38
|
+
- `sympy>=1.10.0`
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from rbca import calc_cos, calc_cos_float, calc_sin, calc_sin_float, calc_tan
|
|
44
|
+
from sympy import sqrtdenest
|
|
45
|
+
|
|
46
|
+
depth = 5
|
|
47
|
+
|
|
48
|
+
cos_expr = calc_cos(37, depth) # SymPy Expr
|
|
49
|
+
sin_expr = calc_sin(37, depth) # SymPy Expr
|
|
50
|
+
tan_expr = calc_tan(37, depth) # SymPy Expr
|
|
51
|
+
|
|
52
|
+
cos_value = calc_cos_float(37, depth) # float
|
|
53
|
+
sin_value = calc_sin_float(37, depth) # float
|
|
54
|
+
|
|
55
|
+
print(cos_expr) # symbolic expression
|
|
56
|
+
print(float(cos_expr)) # numeric projection
|
|
57
|
+
print(sqrtdenest(cos_expr)) # optional radical denesting
|
|
58
|
+
print(cos_value) # practical float result
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Choosing an API Family
|
|
62
|
+
|
|
63
|
+
- Use `calc_cos_pure`, `calc_sin_pure`, and `calc_tan_pure` when you want the mathematically pure symbolic path.
|
|
64
|
+
- This path starts from `[0, 90]` only.
|
|
65
|
+
- Use `calc_cos`, `calc_sin`, and `calc_tan` when you want symbolic results with a more practical anchored start.
|
|
66
|
+
- This path uses anchor angles (`0, 15, 30, 45, 60, 75, 90`) to start from a narrower interval.
|
|
67
|
+
- Use `calc_cos_float`, `calc_sin_float`, and `calc_tan_float` when you want practical float results.
|
|
68
|
+
- This path is implemented with float arithmetic internally.
|
|
69
|
+
|
|
70
|
+
## Core API
|
|
71
|
+
|
|
72
|
+
- `calc_cos(angle, depth) -> Expr`
|
|
73
|
+
- `calc_sin(angle, depth) -> Expr`
|
|
74
|
+
- `calc_tan(angle, depth) -> Expr`
|
|
75
|
+
|
|
76
|
+
- `calc_cos_pure(angle, depth) -> Expr`
|
|
77
|
+
- `calc_sin_pure(angle, depth) -> Expr`
|
|
78
|
+
- `calc_tan_pure(angle, depth) -> Expr`
|
|
79
|
+
|
|
80
|
+
- `calc_cos_float(angle, depth) -> float`
|
|
81
|
+
- `calc_sin_float(angle, depth) -> float`
|
|
82
|
+
- `calc_tan_float(angle, depth) -> float`
|
|
83
|
+
|
|
84
|
+
For symbolic APIs, `angle` accepts `Rational | int | float | str` and is internally converted with `sympy.Rational` where needed.
|
|
85
|
+
|
|
86
|
+
Symbolic input note:
|
|
87
|
+
- `float` inputs are converted through `Rational(float_value)`, which can preserve binary-float artifacts.
|
|
88
|
+
- For exact decimal intent, prefer `str` or `Rational` directly.
|
|
89
|
+
- Example: use `"0.1"` instead of `0.1`.
|
|
90
|
+
|
|
91
|
+
For float APIs, `angle` accepts `int | float | str` and is internally converted with `float(angle)`.
|
|
92
|
+
|
|
93
|
+
`depth` is recursion depth:
|
|
94
|
+
- `depth = 0`: return one of the current interval endpoints
|
|
95
|
+
- larger depth: finer binary partitioning and generally tighter approximation
|
|
96
|
+
|
|
97
|
+
## How It Works
|
|
98
|
+
|
|
99
|
+
1. Normalize input angle to first quadrant (`0~90`) and track output sign.
|
|
100
|
+
2. Choose initial bracket interval:
|
|
101
|
+
- `calc_cos_pure`: `[0, 90]`
|
|
102
|
+
- `calc_cos`: narrower interval between predefined anchor angles when possible.
|
|
103
|
+
- `calc_cos_float`: same anchored interval strategy as `calc_cos`, but with float nodes and float values.
|
|
104
|
+
3. Recursively bisect the interval by midpoint angle.
|
|
105
|
+
4. Compute midpoint cosine via:
|
|
106
|
+
- a product/half-angle style expression using endpoint cosine values.
|
|
107
|
+
5. Recurse until `depth == 0` (or exact angle hit), then apply sign flip if needed.
|
|
108
|
+
|
|
109
|
+
## Notes and Limitations
|
|
110
|
+
|
|
111
|
+
- This library is symbolic-first; expressions can grow quickly as depth increases.
|
|
112
|
+
- For angles where cosine is zero (e.g. `90 + 180k` degrees), `calc_tan` and `calc_tan_pure` return `sympy.zoo`.
|
|
113
|
+
- For the same tangent singularities, `calc_tan_float` lets Python's `ZeroDivisionError` propagate.
|
|
114
|
+
- Inputs with symbolic free variables are rejected during normalization.
|
|
115
|
+
|
|
116
|
+
## Maths
|
|
117
|
+
Condition:
|
|
118
|
+
|
|
119
|
+
$$
|
|
120
|
+
0^\circ \le A,B \le 90^\circ
|
|
121
|
+
$$
|
|
122
|
+
|
|
123
|
+
Using the half-angle formula,
|
|
124
|
+
|
|
125
|
+
$$
|
|
126
|
+
\cos\frac{A+B}{2}
|
|
127
|
+
=
|
|
128
|
+
\sqrt{\frac{1+\cos(A+B)}{2}}
|
|
129
|
+
$$
|
|
130
|
+
|
|
131
|
+
Also, by the angle addition formula,
|
|
132
|
+
|
|
133
|
+
$$
|
|
134
|
+
\cos(A+B)
|
|
135
|
+
=
|
|
136
|
+
\cos A\cos B-\sin A\sin B
|
|
137
|
+
$$
|
|
138
|
+
|
|
139
|
+
Since
|
|
140
|
+
|
|
141
|
+
$$
|
|
142
|
+
0^\circ \le A,B \le 90^\circ
|
|
143
|
+
$$
|
|
144
|
+
|
|
145
|
+
we can use
|
|
146
|
+
|
|
147
|
+
$$
|
|
148
|
+
\sin A=\sqrt{1-\cos^2 A}
|
|
149
|
+
$$
|
|
150
|
+
|
|
151
|
+
and
|
|
152
|
+
|
|
153
|
+
$$
|
|
154
|
+
\sin B=\sqrt{1-\cos^2 B}
|
|
155
|
+
$$
|
|
156
|
+
|
|
157
|
+
Therefore,
|
|
158
|
+
|
|
159
|
+
$$
|
|
160
|
+
\cos(A+B)
|
|
161
|
+
=
|
|
162
|
+
\cos A\cos B
|
|
163
|
+
-
|
|
164
|
+
\sqrt{(1-\cos^2 A)(1-\cos^2 B)}
|
|
165
|
+
$$
|
|
166
|
+
|
|
167
|
+
Substituting this into the half-angle formula finally gives
|
|
168
|
+
|
|
169
|
+
$$
|
|
170
|
+
\boxed{
|
|
171
|
+
\cos\frac{A+B}{2}
|
|
172
|
+
=
|
|
173
|
+
\sqrt{
|
|
174
|
+
\frac{
|
|
175
|
+
1+\cos A\cos B-\sqrt{(1-\cos^2 A)(1-\cos^2 B)}
|
|
176
|
+
}{2}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
$$
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT License. See [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rbca"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Recursive Binary Cosine Approximation"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "imagination12357" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["symbolic-math", "trigonometry", "sympy", "approximation"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.10",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"sympy>=1.10.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/Imagination12357/rbca"
|
|
25
|
+
Repository = "https://github.com/Imagination12357/rbca"
|
|
26
|
+
Issues = "https://github.com/Imagination12357/rbca/issues"
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=8,<9"
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["hatchling"]
|
|
35
|
+
build-backend = "hatchling.build"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/rbca"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from sympy import Rational, Expr, sqrt
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class RbcaNode:
|
|
6
|
+
angle: Rational
|
|
7
|
+
value: Expr
|
|
8
|
+
|
|
9
|
+
def __neg__(self):
|
|
10
|
+
return RbcaNode(self.angle, -self.value)
|
|
11
|
+
|
|
12
|
+
def flipif(self, is_flipped: bool):
|
|
13
|
+
return -self if is_flipped else self
|
|
14
|
+
|
|
15
|
+
def calc_mid_cos(lo: RbcaNode, hi:RbcaNode):
|
|
16
|
+
return RbcaNode(
|
|
17
|
+
(lo.angle + hi.angle) / 2,
|
|
18
|
+
#(sqrt((1+lo.value)*(1+hi.value))-sqrt((1-lo.value)*(1-hi.value)))/2
|
|
19
|
+
sqrt(
|
|
20
|
+
(1 + lo.value*hi.value - sqrt(1 - lo.value**2)*sqrt(1 - hi.value**2)) / 2
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def _validate_depth(depth: int):
|
|
25
|
+
|
|
26
|
+
if not isinstance(depth, int) or isinstance(depth, bool):
|
|
27
|
+
raise TypeError("Depth must be integer.")
|
|
28
|
+
|
|
29
|
+
if depth < 0:
|
|
30
|
+
raise ValueError("Depth must be non-negative.")
|
|
31
|
+
|
|
32
|
+
def recursive_approx_cos(lo: RbcaNode, hi:RbcaNode, target:Rational, depth:int) -> RbcaNode:
|
|
33
|
+
|
|
34
|
+
_validate_depth(depth)
|
|
35
|
+
|
|
36
|
+
if depth == 0:
|
|
37
|
+
return lo if target - lo.angle < hi.angle - target else hi
|
|
38
|
+
|
|
39
|
+
mid = calc_mid_cos(lo, hi)
|
|
40
|
+
|
|
41
|
+
if mid.angle == target:
|
|
42
|
+
return mid
|
|
43
|
+
|
|
44
|
+
if target < mid.angle:
|
|
45
|
+
return recursive_approx_cos(lo, mid, target, depth-1)
|
|
46
|
+
|
|
47
|
+
else:
|
|
48
|
+
return recursive_approx_cos(mid, hi, target, depth-1)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from .core import _validate_depth
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class RbcaNodeF:
|
|
7
|
+
angle: float
|
|
8
|
+
value: float
|
|
9
|
+
|
|
10
|
+
def __neg__(self):
|
|
11
|
+
return RbcaNodeF(self.angle, -self.value)
|
|
12
|
+
|
|
13
|
+
def flipif(self, is_flipped: bool):
|
|
14
|
+
return -self if is_flipped else self
|
|
15
|
+
|
|
16
|
+
def calc_mid_cos_f(lo: RbcaNodeF, hi:RbcaNodeF):
|
|
17
|
+
return RbcaNodeF(
|
|
18
|
+
(lo.angle + hi.angle) / 2,
|
|
19
|
+
(math.sqrt((1+lo.value)*(1+hi.value))-math.sqrt((1-lo.value)*(1-hi.value)))/2
|
|
20
|
+
#math.sqrt(
|
|
21
|
+
# (1 + lo.value*hi.value - math.sqrt(1 - lo.value**2)*math.sqrt(1 - hi.value**2)) / 2
|
|
22
|
+
#)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def recursive_approx_cos_f(lo: RbcaNodeF, hi:RbcaNodeF, target:float, depth:int) -> RbcaNodeF:
|
|
26
|
+
|
|
27
|
+
_validate_depth(depth)
|
|
28
|
+
|
|
29
|
+
if depth == 0:
|
|
30
|
+
return lo if target - lo.angle < hi.angle - target else hi
|
|
31
|
+
|
|
32
|
+
mid = calc_mid_cos_f(lo, hi)
|
|
33
|
+
|
|
34
|
+
if mid.angle == target:
|
|
35
|
+
return mid
|
|
36
|
+
|
|
37
|
+
if target < mid.angle:
|
|
38
|
+
return recursive_approx_cos_f(lo, mid, target, depth-1)
|
|
39
|
+
|
|
40
|
+
else:
|
|
41
|
+
return recursive_approx_cos_f(mid, hi, target, depth-1)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from sympy import Rational, Expr, sqrt, zoo
|
|
2
|
+
from .core import RbcaNode, recursive_approx_cos, _validate_depth
|
|
3
|
+
|
|
4
|
+
def _rationalize(angle:Rational|int|float|str) -> Rational:
|
|
5
|
+
try:
|
|
6
|
+
return Rational(angle)
|
|
7
|
+
except Exception as e:
|
|
8
|
+
raise ValueError("Angle must be representable as a single Rational.") from e
|
|
9
|
+
|
|
10
|
+
def normalize_cos(angle:Rational|int|float|str) -> tuple[Rational, bool]:
|
|
11
|
+
|
|
12
|
+
norm_angle = _rationalize(angle) % 360
|
|
13
|
+
|
|
14
|
+
if norm_angle <= 90:
|
|
15
|
+
final_angle = norm_angle
|
|
16
|
+
is_flipped = False
|
|
17
|
+
|
|
18
|
+
elif norm_angle <= 180:
|
|
19
|
+
final_angle = 180 - norm_angle
|
|
20
|
+
is_flipped = True
|
|
21
|
+
|
|
22
|
+
elif norm_angle <= 270:
|
|
23
|
+
final_angle = norm_angle - 180
|
|
24
|
+
is_flipped = True
|
|
25
|
+
|
|
26
|
+
else:
|
|
27
|
+
final_angle = 360 - norm_angle
|
|
28
|
+
is_flipped = False
|
|
29
|
+
|
|
30
|
+
return final_angle, is_flipped
|
|
31
|
+
|
|
32
|
+
def calc_cos_pure(angle:Rational|int|float|str, depth:int) -> Expr:
|
|
33
|
+
|
|
34
|
+
_validate_depth(depth)
|
|
35
|
+
|
|
36
|
+
norm_angle, is_flipped = normalize_cos(angle)
|
|
37
|
+
|
|
38
|
+
return recursive_approx_cos(
|
|
39
|
+
RbcaNode(Rational(0), 1),
|
|
40
|
+
RbcaNode(Rational(90), 0),
|
|
41
|
+
norm_angle,
|
|
42
|
+
depth
|
|
43
|
+
).flipif(is_flipped).value
|
|
44
|
+
|
|
45
|
+
def calc_sin_pure(angle:Rational|int|float|str, depth:int) -> Expr:
|
|
46
|
+
return calc_cos_pure(90 - _rationalize(angle), depth)
|
|
47
|
+
|
|
48
|
+
def calc_tan_pure(angle:Rational|int|float|str, depth:int) -> Expr:
|
|
49
|
+
try:
|
|
50
|
+
return calc_sin_pure(angle, depth) / calc_cos_pure(angle, depth)
|
|
51
|
+
except ZeroDivisionError:
|
|
52
|
+
return zoo
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def calc_cos(angle:Rational|int|float|str, depth:int) -> Expr:
|
|
56
|
+
|
|
57
|
+
_validate_depth(depth)
|
|
58
|
+
|
|
59
|
+
norm_angle, is_flipped = normalize_cos(angle)
|
|
60
|
+
|
|
61
|
+
if norm_angle == 0:
|
|
62
|
+
return RbcaNode(Rational(0), Rational(1)).flipif(is_flipped).value
|
|
63
|
+
|
|
64
|
+
elif norm_angle < 15:
|
|
65
|
+
return recursive_approx_cos(
|
|
66
|
+
RbcaNode(Rational(0), Rational(1)),
|
|
67
|
+
RbcaNode(Rational(15), sqrt(6)/4 + sqrt(2)/4),
|
|
68
|
+
norm_angle,
|
|
69
|
+
depth
|
|
70
|
+
).flipif(is_flipped).value
|
|
71
|
+
|
|
72
|
+
elif norm_angle == 15:
|
|
73
|
+
return RbcaNode(Rational(15), sqrt(6)/4 + sqrt(2)/4).flipif(is_flipped).value
|
|
74
|
+
|
|
75
|
+
elif norm_angle < 30:
|
|
76
|
+
return recursive_approx_cos(
|
|
77
|
+
RbcaNode(Rational(15), sqrt(6)/4 + sqrt(2)/4),
|
|
78
|
+
RbcaNode(Rational(30), sqrt(3)/2),
|
|
79
|
+
norm_angle,
|
|
80
|
+
depth
|
|
81
|
+
).flipif(is_flipped).value
|
|
82
|
+
|
|
83
|
+
elif norm_angle == 30:
|
|
84
|
+
return RbcaNode(Rational(30), sqrt(3)/2).flipif(is_flipped).value
|
|
85
|
+
|
|
86
|
+
elif norm_angle < 45:
|
|
87
|
+
return recursive_approx_cos(
|
|
88
|
+
RbcaNode(Rational(30), sqrt(3)/2),
|
|
89
|
+
RbcaNode(Rational(45), sqrt(2)/2),
|
|
90
|
+
norm_angle,
|
|
91
|
+
depth
|
|
92
|
+
).flipif(is_flipped).value
|
|
93
|
+
|
|
94
|
+
elif norm_angle == 45:
|
|
95
|
+
return RbcaNode(Rational(45), sqrt(2)/2).flipif(is_flipped).value
|
|
96
|
+
|
|
97
|
+
elif norm_angle < 60:
|
|
98
|
+
return recursive_approx_cos(
|
|
99
|
+
RbcaNode(Rational(45), sqrt(2)/2),
|
|
100
|
+
RbcaNode(Rational(60), Rational(1,2)),
|
|
101
|
+
norm_angle,
|
|
102
|
+
depth
|
|
103
|
+
).flipif(is_flipped).value
|
|
104
|
+
|
|
105
|
+
elif norm_angle == 60:
|
|
106
|
+
return RbcaNode(Rational(60), Rational(1,2)).flipif(is_flipped).value
|
|
107
|
+
|
|
108
|
+
elif norm_angle < 75:
|
|
109
|
+
return recursive_approx_cos(
|
|
110
|
+
RbcaNode(Rational(60), Rational(1,2)),
|
|
111
|
+
RbcaNode(Rational(75), sqrt(6)/4 - sqrt(2)/4),
|
|
112
|
+
norm_angle,
|
|
113
|
+
depth
|
|
114
|
+
).flipif(is_flipped).value
|
|
115
|
+
|
|
116
|
+
elif norm_angle == 75:
|
|
117
|
+
return RbcaNode(Rational(75), sqrt(6)/4 - sqrt(2)/4).flipif(is_flipped).value
|
|
118
|
+
|
|
119
|
+
elif norm_angle < 90:
|
|
120
|
+
return recursive_approx_cos(
|
|
121
|
+
RbcaNode(Rational(75), sqrt(6)/4 - sqrt(2)/4),
|
|
122
|
+
RbcaNode(Rational(90), 0),
|
|
123
|
+
norm_angle,
|
|
124
|
+
depth
|
|
125
|
+
).flipif(is_flipped).value
|
|
126
|
+
|
|
127
|
+
elif norm_angle == 90:
|
|
128
|
+
return RbcaNode(Rational(90), 0).flipif(is_flipped).value
|
|
129
|
+
|
|
130
|
+
else:
|
|
131
|
+
raise RuntimeError(f"Normalization is wrong with something\nnorm_angle:{norm_angle}")
|
|
132
|
+
|
|
133
|
+
def calc_sin(angle:Rational|int|float|str, depth:int) -> Expr:
|
|
134
|
+
return calc_cos(90 - _rationalize(angle), depth)
|
|
135
|
+
|
|
136
|
+
def calc_tan(angle:Rational|int|float|str, depth:int) -> Expr:
|
|
137
|
+
try:
|
|
138
|
+
return calc_sin(angle, depth) / calc_cos(angle, depth)
|
|
139
|
+
except ZeroDivisionError:
|
|
140
|
+
return zoo
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
from sympy import latex, sqrtdenest
|
|
144
|
+
result = sqrtdenest(calc_cos(37, 5))
|
|
145
|
+
print(latex(result))
|
|
146
|
+
print(float(result))
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from .core_float import RbcaNodeF, recursive_approx_cos_f, _validate_depth
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
def normalize_cos(angle:int|float|str) -> tuple[float, bool]:
|
|
5
|
+
|
|
6
|
+
norm_angle = float(angle) % 360
|
|
7
|
+
|
|
8
|
+
if norm_angle <= 90:
|
|
9
|
+
final_angle = norm_angle
|
|
10
|
+
is_flipped = False
|
|
11
|
+
|
|
12
|
+
elif norm_angle <= 180:
|
|
13
|
+
final_angle = 180 - norm_angle
|
|
14
|
+
is_flipped = True
|
|
15
|
+
|
|
16
|
+
elif norm_angle <= 270:
|
|
17
|
+
final_angle = norm_angle - 180
|
|
18
|
+
is_flipped = True
|
|
19
|
+
|
|
20
|
+
else:
|
|
21
|
+
final_angle = 360 - norm_angle
|
|
22
|
+
is_flipped = False
|
|
23
|
+
|
|
24
|
+
return final_angle, is_flipped
|
|
25
|
+
|
|
26
|
+
def calc_cos_float(angle:int|float|str, depth:int) -> float:
|
|
27
|
+
|
|
28
|
+
_validate_depth(depth)
|
|
29
|
+
|
|
30
|
+
norm_angle, is_flipped = normalize_cos(angle)
|
|
31
|
+
|
|
32
|
+
if norm_angle == 0:
|
|
33
|
+
return RbcaNodeF(0, 1).flipif(is_flipped).value
|
|
34
|
+
|
|
35
|
+
elif norm_angle < 15:
|
|
36
|
+
return recursive_approx_cos_f(
|
|
37
|
+
RbcaNodeF(0, 1),
|
|
38
|
+
RbcaNodeF(15, math.sqrt(6)/4 + math.sqrt(2)/4),
|
|
39
|
+
norm_angle,
|
|
40
|
+
depth
|
|
41
|
+
).flipif(is_flipped).value
|
|
42
|
+
|
|
43
|
+
elif norm_angle == 15:
|
|
44
|
+
return RbcaNodeF(15, math.sqrt(6)/4 + math.sqrt(2)/4).flipif(is_flipped).value
|
|
45
|
+
|
|
46
|
+
elif norm_angle < 30:
|
|
47
|
+
return recursive_approx_cos_f(
|
|
48
|
+
RbcaNodeF(15, math.sqrt(6)/4 + math.sqrt(2)/4),
|
|
49
|
+
RbcaNodeF(30, math.sqrt(3)/2),
|
|
50
|
+
norm_angle,
|
|
51
|
+
depth
|
|
52
|
+
).flipif(is_flipped).value
|
|
53
|
+
|
|
54
|
+
elif norm_angle == 30:
|
|
55
|
+
return RbcaNodeF(30, math.sqrt(3)/2).flipif(is_flipped).value
|
|
56
|
+
|
|
57
|
+
elif norm_angle < 45:
|
|
58
|
+
return recursive_approx_cos_f(
|
|
59
|
+
RbcaNodeF(30, math.sqrt(3)/2),
|
|
60
|
+
RbcaNodeF(45, math.sqrt(2)/2),
|
|
61
|
+
norm_angle,
|
|
62
|
+
depth
|
|
63
|
+
).flipif(is_flipped).value
|
|
64
|
+
|
|
65
|
+
elif norm_angle == 45:
|
|
66
|
+
return RbcaNodeF(45, math.sqrt(2)/2).flipif(is_flipped).value
|
|
67
|
+
|
|
68
|
+
elif norm_angle < 60:
|
|
69
|
+
return recursive_approx_cos_f(
|
|
70
|
+
RbcaNodeF(45, math.sqrt(2)/2),
|
|
71
|
+
RbcaNodeF(60, 1/2),
|
|
72
|
+
norm_angle,
|
|
73
|
+
depth
|
|
74
|
+
).flipif(is_flipped).value
|
|
75
|
+
|
|
76
|
+
elif norm_angle == 60:
|
|
77
|
+
return RbcaNodeF(60, 1/2).flipif(is_flipped).value
|
|
78
|
+
|
|
79
|
+
elif norm_angle < 75:
|
|
80
|
+
return recursive_approx_cos_f(
|
|
81
|
+
RbcaNodeF(60, 1/2),
|
|
82
|
+
RbcaNodeF(75, math.sqrt(6)/4 - math.sqrt(2)/4),
|
|
83
|
+
norm_angle,
|
|
84
|
+
depth
|
|
85
|
+
).flipif(is_flipped).value
|
|
86
|
+
|
|
87
|
+
elif norm_angle == 75:
|
|
88
|
+
return RbcaNodeF(75, math.sqrt(6)/4 - math.sqrt(2)/4).flipif(is_flipped).value
|
|
89
|
+
|
|
90
|
+
elif norm_angle < 90:
|
|
91
|
+
return recursive_approx_cos_f(
|
|
92
|
+
RbcaNodeF(75, math.sqrt(6)/4 - math.sqrt(2)/4),
|
|
93
|
+
RbcaNodeF(90, 0),
|
|
94
|
+
norm_angle,
|
|
95
|
+
depth
|
|
96
|
+
).flipif(is_flipped).value
|
|
97
|
+
|
|
98
|
+
elif norm_angle == 90:
|
|
99
|
+
return RbcaNodeF(90, 0).flipif(is_flipped).value
|
|
100
|
+
|
|
101
|
+
else:
|
|
102
|
+
raise RuntimeError(f"Normalization is wrong with something\nnorm_angle:{norm_angle}")
|
|
103
|
+
|
|
104
|
+
def calc_sin_float(angle:int|float|str, depth:int) -> float:
|
|
105
|
+
return calc_cos_float(90 - float(angle), depth)
|
|
106
|
+
|
|
107
|
+
def calc_tan_float(angle:int|float|str, depth:int) -> float:
|
|
108
|
+
return calc_sin_float(angle, depth) / calc_cos_float(angle, depth) # Let the ZeroDivisionError go
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
print(calc_cos_float(37,10))
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from sympy import Rational, sqrt, zoo
|
|
3
|
+
|
|
4
|
+
from rbca import (
|
|
5
|
+
calc_cos,
|
|
6
|
+
calc_cos_float,
|
|
7
|
+
calc_sin,
|
|
8
|
+
calc_sin_float,
|
|
9
|
+
calc_tan,
|
|
10
|
+
calc_tan_float,
|
|
11
|
+
calc_tan_pure,
|
|
12
|
+
)
|
|
13
|
+
from rbca.lib import normalize_cos
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize(
|
|
17
|
+
("angle", "expected"),
|
|
18
|
+
[
|
|
19
|
+
(0, (Rational(0), False)),
|
|
20
|
+
(90, (Rational(90), False)),
|
|
21
|
+
(180, (Rational(0), True)),
|
|
22
|
+
(270, (Rational(90), True)),
|
|
23
|
+
(360, (Rational(0), False)),
|
|
24
|
+
(-90, (Rational(90), True)),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test_normalize_cos_boundaries(angle, expected):
|
|
28
|
+
assert normalize_cos(angle) == expected
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
("angle", "expected"),
|
|
33
|
+
[
|
|
34
|
+
(0, Rational(1)),
|
|
35
|
+
(30, sqrt(3) / 2),
|
|
36
|
+
(45, sqrt(2) / 2),
|
|
37
|
+
(60, Rational(1, 2)),
|
|
38
|
+
(90, Rational(0)),
|
|
39
|
+
],
|
|
40
|
+
)
|
|
41
|
+
def test_calc_cos_anchor_values(angle, expected):
|
|
42
|
+
assert calc_cos(angle, 2) == expected
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.parametrize(
|
|
46
|
+
("angle", "expected"),
|
|
47
|
+
[
|
|
48
|
+
(0, Rational(0)),
|
|
49
|
+
(30, Rational(1, 2)),
|
|
50
|
+
(45, sqrt(2) / 2),
|
|
51
|
+
(60, sqrt(3) / 2),
|
|
52
|
+
(90, Rational(1)),
|
|
53
|
+
],
|
|
54
|
+
)
|
|
55
|
+
def test_calc_sin_anchor_values(angle, expected):
|
|
56
|
+
assert calc_sin(angle, 2) == expected
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_depth_negative_raises():
|
|
60
|
+
with pytest.raises(ValueError):
|
|
61
|
+
calc_cos(30, -1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_depth_non_integer_raises():
|
|
65
|
+
with pytest.raises(TypeError):
|
|
66
|
+
calc_cos(30, 1.5)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_depth_bool_raises():
|
|
70
|
+
with pytest.raises(TypeError):
|
|
71
|
+
calc_cos(30, True)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_calc_tan_90_behavior():
|
|
75
|
+
assert calc_tan(90, 2) == zoo
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_calc_tan_pure_90_behavior():
|
|
79
|
+
assert calc_tan_pure(90, 2) == zoo
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.parametrize(
|
|
83
|
+
("angle", "expected"),
|
|
84
|
+
[
|
|
85
|
+
(0, 1.0),
|
|
86
|
+
(30, 3**0.5 / 2),
|
|
87
|
+
(45, 2**0.5 / 2),
|
|
88
|
+
(60, 0.5),
|
|
89
|
+
(90, 0.0),
|
|
90
|
+
],
|
|
91
|
+
)
|
|
92
|
+
def test_calc_cos_float_anchor_values(angle, expected):
|
|
93
|
+
assert calc_cos_float(angle, 2) == pytest.approx(expected)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.mark.parametrize(
|
|
97
|
+
("angle", "expected"),
|
|
98
|
+
[
|
|
99
|
+
(0, 0.0),
|
|
100
|
+
(30, 0.5),
|
|
101
|
+
(45, 2**0.5 / 2),
|
|
102
|
+
(60, 3**0.5 / 2),
|
|
103
|
+
(90, 1.0),
|
|
104
|
+
],
|
|
105
|
+
)
|
|
106
|
+
def test_calc_sin_float_anchor_values(angle, expected):
|
|
107
|
+
assert calc_sin_float(angle, 2) == pytest.approx(expected)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_calc_tan_float_90_behavior():
|
|
111
|
+
with pytest.raises(ZeroDivisionError):
|
|
112
|
+
calc_tan_float(90, 2)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_calc_cos_float_depth_negative_raises():
|
|
116
|
+
with pytest.raises(ValueError):
|
|
117
|
+
calc_cos_float(30, -1)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_calc_cos_float_depth_non_integer_raises():
|
|
121
|
+
with pytest.raises(TypeError):
|
|
122
|
+
calc_cos_float(30, 1.5)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_calc_cos_float_angle_non_floatable_raises():
|
|
126
|
+
with pytest.raises(ValueError):
|
|
127
|
+
calc_cos_float("Not-a-float", 2)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_angle_non_Rational_raises():
|
|
131
|
+
with pytest.raises(ValueError):
|
|
132
|
+
calc_cos("Not-a-Rational", 2)
|