FastQuat 1.0b1__tar.gz → 1.0b2__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.
- {fastquat-1.0b1 → fastquat-1.0b2}/PKG-INFO +22 -7
- {fastquat-1.0b1 → fastquat-1.0b2}/README.md +20 -5
- fastquat-1.0b2/docs/README.md +27 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/source/index.md +17 -13
- {fastquat-1.0b1 → fastquat-1.0b2}/src/fastquat/quaternion.py +6 -0
- fastquat-1.0b2/tests/test_indexing.py +208 -0
- fastquat-1.0b1/docs/README.md +0 -96
- {fastquat-1.0b1 → fastquat-1.0b2}/.github/workflows/ci.yml +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/.github/workflows/release.yml +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/.gitignore +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/.pre-commit-config.yaml +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/.readthedocs.yaml +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/.gitignore +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/Makefile +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/source/api/quaternion.md +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/source/conf.py +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/source/development.md +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/source/user-guide/getting-started.ipynb +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/source/user-guide/installation.md +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/source/user-guide/tutorial-rotations.ipynb +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/docs/source/user-guide/tutorial-slerp.ipynb +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/pyproject.toml +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/src/fastquat/__init__.py +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/tests/conftest.py +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/tests/test_base.py +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/tests/test_math.py +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/tests/test_pow.py +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/tests/test_rotation.py +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/tests/test_tensor.py +0 -0
- {fastquat-1.0b1 → fastquat-1.0b2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: FastQuat
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.0b2
|
|
4
4
|
Summary: High-performance quaternions with JAX support
|
|
5
5
|
Project-URL: homepage, https://fastquat.readthedocs.io
|
|
6
6
|
Project-URL: repository, https://github.com/CMBSciPol/fastquat
|
|
@@ -55,11 +55,26 @@ from fastquat import Quaternion
|
|
|
55
55
|
# Create quaternions
|
|
56
56
|
q1 = Quaternion(1) # Identity quaternion
|
|
57
57
|
q2 = Quaternion(0.7071, 0.7071, 0.0, 0.0) # 90° rotation around x-axis
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
q = Quaternion(1.0, 0.1, 0.2, 0.3)
|
|
59
|
+
p = 2
|
|
60
|
+
|
|
61
|
+
# Arithmetic
|
|
62
|
+
q_sum = q1 + q2
|
|
63
|
+
q_diff = q1 - q2
|
|
64
|
+
q_product = q1 * q2
|
|
65
|
+
q_power = q**p
|
|
66
|
+
|
|
67
|
+
# Normalization
|
|
68
|
+
norm = abs(q) # Quaternion norm
|
|
69
|
+
q_unit = q.normalize() # Unit quaternion
|
|
70
|
+
|
|
71
|
+
# Conjugation and inverse
|
|
72
|
+
q_conj = q.conj() # Conjugate
|
|
73
|
+
q_inv = 1 / q # Inverse, or q ** -1
|
|
74
|
+
|
|
75
|
+
# Other operations
|
|
76
|
+
q_log = q.log()
|
|
77
|
+
q_exp = q.exp()
|
|
63
78
|
|
|
64
79
|
# Rotate vectors
|
|
65
80
|
vector = jnp.array([1.0, 0.0, 0.0])
|
|
@@ -32,11 +32,26 @@ from fastquat import Quaternion
|
|
|
32
32
|
# Create quaternions
|
|
33
33
|
q1 = Quaternion(1) # Identity quaternion
|
|
34
34
|
q2 = Quaternion(0.7071, 0.7071, 0.0, 0.0) # 90° rotation around x-axis
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
q = Quaternion(1.0, 0.1, 0.2, 0.3)
|
|
36
|
+
p = 2
|
|
37
|
+
|
|
38
|
+
# Arithmetic
|
|
39
|
+
q_sum = q1 + q2
|
|
40
|
+
q_diff = q1 - q2
|
|
41
|
+
q_product = q1 * q2
|
|
42
|
+
q_power = q**p
|
|
43
|
+
|
|
44
|
+
# Normalization
|
|
45
|
+
norm = abs(q) # Quaternion norm
|
|
46
|
+
q_unit = q.normalize() # Unit quaternion
|
|
47
|
+
|
|
48
|
+
# Conjugation and inverse
|
|
49
|
+
q_conj = q.conj() # Conjugate
|
|
50
|
+
q_inv = 1 / q # Inverse, or q ** -1
|
|
51
|
+
|
|
52
|
+
# Other operations
|
|
53
|
+
q_log = q.log()
|
|
54
|
+
q_exp = q.exp()
|
|
40
55
|
|
|
41
56
|
# Rotate vectors
|
|
42
57
|
vector = jnp.array([1.0, 0.0, 0.0])
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Documentation Build Instructions
|
|
2
|
+
|
|
3
|
+
This documentation uses **PEP 735 dependency groups** instead of separate requirements files to avoid duplication.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Building Documentation Locally
|
|
7
|
+
|
|
8
|
+
The dependency group `docs` is included in the group `dev` which is installed by default when using ̀uv` (recommended).
|
|
9
|
+
No extra step other than setting up and activating the virtual environment is required:
|
|
10
|
+
```bash
|
|
11
|
+
# From the docs/ directory
|
|
12
|
+
uv sync --locked --group cuda12
|
|
13
|
+
source .venv/bin/activate
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If you're not using uv, you can install the docs dependencies manually:
|
|
17
|
+
```bash
|
|
18
|
+
# Install the project with docs dependencies (pip >= 25.1)
|
|
19
|
+
pip install --upgrade pip
|
|
20
|
+
pip install -e . --group dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then build the docs
|
|
24
|
+
```bash
|
|
25
|
+
cd docs
|
|
26
|
+
make html
|
|
27
|
+
```
|
|
@@ -19,26 +19,30 @@ FastQuat provides optimized quaternion operations with full JAX compatibility, f
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
21
21
|
```python
|
|
22
|
-
import jax.numpy as jnp
|
|
23
22
|
from fastquat import Quaternion
|
|
24
23
|
|
|
25
24
|
# Create quaternions
|
|
26
25
|
q1 = Quaternion(1.0) # Identity quaternion
|
|
27
26
|
q2 = Quaternion(0.7071, 0.7071, 0.0, 0.0) # 90° rotation around x-axis
|
|
28
|
-
|
|
29
|
-
# Quaternion operations
|
|
30
|
-
q3 = q1 * q2 # Multiplication
|
|
31
|
-
q_inv = 1 / q1 # Inverse
|
|
32
|
-
q_norm = q1.normalize() # Normalization
|
|
33
|
-
|
|
34
|
-
# Rotate vectors
|
|
35
|
-
vector = jnp.array([1.0, 0.0, 0.0])
|
|
36
|
-
rotated = q2.rotate_vector(vector)
|
|
37
|
-
|
|
38
|
-
# Spherical interpolation (SLERP)
|
|
39
|
-
interpolated = q1.slerp(q2, t=0.5) # Halfway between q1 and q2
|
|
40
27
|
```
|
|
41
28
|
|
|
29
|
+
## Quaternion Operations
|
|
30
|
+
|
|
31
|
+
| Operation | Syntax | Description |
|
|
32
|
+
|-----------|--------|-------------|
|
|
33
|
+
| Addition | `q1 + q2` | Component-wise addition |
|
|
34
|
+
| Subtraction | `q1 - q2` | Component-wise subtraction |
|
|
35
|
+
| Multiplication | `q1 * q2` | Hamilton product |
|
|
36
|
+
| Exponentiation | `q ** p` | Quaternion power |
|
|
37
|
+
| Inverse | `1 / q` | Multiplicative inverse |
|
|
38
|
+
| Norm | `abs(q)` | Quaternion magnitude |
|
|
39
|
+
| Normalization | `q.normalize()` | Unit quaternion |
|
|
40
|
+
| Conjugate | `q.conj()` | Quaternion conjugate |
|
|
41
|
+
| Rotation | `q.rotate_vector(v)` | Rotate 3D vector |
|
|
42
|
+
| SLERP | `q1.slerp(q2, t)` | Spherical interpolation |
|
|
43
|
+
| Log | `q.log()` | Quaternion logarithm |
|
|
44
|
+
| Exp | `q.exp()` | Quaternion exponential |
|
|
45
|
+
|
|
42
46
|
```{toctree}
|
|
43
47
|
:maxdepth: 2
|
|
44
48
|
:caption: User Guide
|
|
@@ -292,6 +292,12 @@ class Quaternion:
|
|
|
292
292
|
for i in range(self.shape[0]):
|
|
293
293
|
yield Quaternion.from_array(self.wxyz[i])
|
|
294
294
|
|
|
295
|
+
def __getitem__(self, idx: Any) -> Self:
|
|
296
|
+
"""Index or slice the tensor of quaternions."""
|
|
297
|
+
if not isinstance(idx, tuple):
|
|
298
|
+
idx = (idx,)
|
|
299
|
+
return Quaternion.from_array(self.wxyz[(*idx, slice(None))])
|
|
300
|
+
|
|
295
301
|
def __pos__(self) -> Self:
|
|
296
302
|
"""Quaternion positive."""
|
|
297
303
|
return self
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Indexing tests for Quaternion class.
|
|
2
|
+
|
|
3
|
+
Tests for __getitem__: integers, slices, boolean masks, fancy integer arrays, None
|
|
4
|
+
(newaxis), Ellipsis, and combinations thereof. The component axis (w, x, y, z) must
|
|
5
|
+
never be reachable by these indices -- only the batch shape is indexed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import jax
|
|
9
|
+
import jax.numpy as jnp
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from fastquat.quaternion import Quaternion
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _arange_quat(*shape: int) -> Quaternion:
|
|
17
|
+
"""A Quaternion whose wxyz array is `jnp.arange(prod(shape) * 4).reshape(*shape, 4)`."""
|
|
18
|
+
size = 1
|
|
19
|
+
for dim in shape:
|
|
20
|
+
size *= dim
|
|
21
|
+
return Quaternion.from_array(jnp.arange(size * 4, dtype=jnp.float32).reshape(*shape, 4))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.parametrize(
|
|
25
|
+
'idx',
|
|
26
|
+
[0, -1, 2, slice(1, 4), slice(None, None, 2), slice(None, None, -1), slice(2, None)],
|
|
27
|
+
)
|
|
28
|
+
@pytest.mark.parametrize('do_jit', [False, True])
|
|
29
|
+
def test_getitem_int_and_slice(idx, do_jit):
|
|
30
|
+
"""Basic and strided/negative slicing over the leading axis."""
|
|
31
|
+
|
|
32
|
+
# `idx` is a Python-level constant closed over by `func`, not a traced argument.
|
|
33
|
+
def func(q):
|
|
34
|
+
return q[idx]
|
|
35
|
+
|
|
36
|
+
if do_jit:
|
|
37
|
+
func = jax.jit(func)
|
|
38
|
+
|
|
39
|
+
q = _arange_quat(6)
|
|
40
|
+
result = func(q)
|
|
41
|
+
expected = q.wxyz[idx]
|
|
42
|
+
assert result.wxyz.shape == expected.shape
|
|
43
|
+
assert jnp.allclose(result.wxyz, expected)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_getitem_boolean_mask():
|
|
47
|
+
"""Boolean-mask indexing preserves the component axis.
|
|
48
|
+
|
|
49
|
+
Not parametrized over `do_jit`: a traced boolean mask is non-concrete under `jit`, exactly
|
|
50
|
+
as it would be for a plain JAX array (`arr[mask]`) -- this is a JAX limitation, not
|
|
51
|
+
something `Quaternion.__getitem__` needs to work around.
|
|
52
|
+
"""
|
|
53
|
+
mask = jnp.array([True, False, True, True, False])
|
|
54
|
+
q = _arange_quat(5)
|
|
55
|
+
result = q[mask]
|
|
56
|
+
assert result.shape == (3,)
|
|
57
|
+
assert jnp.allclose(result.wxyz, q.wxyz[mask])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.parametrize('do_jit', [False, True])
|
|
61
|
+
def test_getitem_fancy_integer_array(do_jit):
|
|
62
|
+
"""Gather with a dynamic integer index array (e.g. batched detector selection)."""
|
|
63
|
+
idx = jnp.array([3, 0, 0, 4])
|
|
64
|
+
|
|
65
|
+
def func(q, i):
|
|
66
|
+
return q[i]
|
|
67
|
+
|
|
68
|
+
if do_jit:
|
|
69
|
+
func = jax.jit(func)
|
|
70
|
+
|
|
71
|
+
q = _arange_quat(5)
|
|
72
|
+
result = func(q, idx)
|
|
73
|
+
assert result.shape == (4,)
|
|
74
|
+
assert jnp.allclose(result.wxyz, q.wxyz[idx])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.parametrize('do_jit', [False, True])
|
|
78
|
+
def test_getitem_fancy_integer_array_out_of_range_clamps(do_jit):
|
|
79
|
+
"""Out-of-range indices clamp like default JAX gather semantics (no IndexError)."""
|
|
80
|
+
idx = jnp.array([0, 10, -10])
|
|
81
|
+
|
|
82
|
+
def func(q, i):
|
|
83
|
+
return q[i]
|
|
84
|
+
|
|
85
|
+
if do_jit:
|
|
86
|
+
func = jax.jit(func)
|
|
87
|
+
|
|
88
|
+
q = _arange_quat(5)
|
|
89
|
+
result = func(q, idx)
|
|
90
|
+
assert jnp.allclose(result.wxyz, q.wxyz[idx])
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.parametrize(
|
|
94
|
+
'idx,expected_shape',
|
|
95
|
+
[
|
|
96
|
+
((None, slice(None)), (1, 5)),
|
|
97
|
+
((slice(None), None), (5, 1)),
|
|
98
|
+
((None, slice(None), None), (1, 5, 1)),
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
@pytest.mark.parametrize('do_jit', [False, True])
|
|
102
|
+
def test_getitem_none_newaxis(idx, expected_shape, do_jit):
|
|
103
|
+
"""None/newaxis inserts a batch axis without disturbing the components."""
|
|
104
|
+
|
|
105
|
+
def func(q):
|
|
106
|
+
return q[idx]
|
|
107
|
+
|
|
108
|
+
if do_jit:
|
|
109
|
+
func = jax.jit(func)
|
|
110
|
+
|
|
111
|
+
q = _arange_quat(5)
|
|
112
|
+
result = func(q)
|
|
113
|
+
assert result.shape == expected_shape
|
|
114
|
+
assert jnp.allclose(result.wxyz, q.wxyz[idx])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.parametrize('do_jit', [False, True])
|
|
118
|
+
def test_getitem_ellipsis_with_none(do_jit):
|
|
119
|
+
"""Ellipsis combined with None on a multi-dimensional batch shape."""
|
|
120
|
+
|
|
121
|
+
def func(q):
|
|
122
|
+
return q[None, ...]
|
|
123
|
+
|
|
124
|
+
if do_jit:
|
|
125
|
+
func = jax.jit(func)
|
|
126
|
+
|
|
127
|
+
q = _arange_quat(2, 3)
|
|
128
|
+
result = func(q)
|
|
129
|
+
assert result.shape == (1, 2, 3)
|
|
130
|
+
assert jnp.allclose(result.wxyz, q.wxyz[None, ...])
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@pytest.mark.parametrize('do_jit', [False, True])
|
|
134
|
+
def test_getitem_multidim_tuple(do_jit):
|
|
135
|
+
"""A tuple index spanning multiple batch dimensions."""
|
|
136
|
+
|
|
137
|
+
def func(q):
|
|
138
|
+
return q[1:, 0]
|
|
139
|
+
|
|
140
|
+
if do_jit:
|
|
141
|
+
func = jax.jit(func)
|
|
142
|
+
|
|
143
|
+
q = _arange_quat(3, 4)
|
|
144
|
+
result = func(q)
|
|
145
|
+
assert result.shape == (2,)
|
|
146
|
+
assert jnp.allclose(result.wxyz, q.wxyz[1:, 0])
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_getitem_scalar_index_reduces_ndim():
|
|
150
|
+
"""Indexing with a plain int drops that axis, exactly like a JAX array."""
|
|
151
|
+
q = _arange_quat(6)
|
|
152
|
+
result = q[2]
|
|
153
|
+
assert result.shape == ()
|
|
154
|
+
assert jnp.allclose(result.wxyz, q.wxyz[2])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_getitem_never_indexes_component_axis():
|
|
158
|
+
"""A full-rank slice tuple must still leave all 4 components intact."""
|
|
159
|
+
q = _arange_quat(3)
|
|
160
|
+
# `q[:]` only has one explicit index; the hidden component axis must stay whole.
|
|
161
|
+
result = q[:]
|
|
162
|
+
assert result.wxyz.shape == (3, 4)
|
|
163
|
+
assert jnp.allclose(result.wxyz, q.wxyz)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@pytest.mark.parametrize('do_jit', [False, True])
|
|
167
|
+
def test_getitem_broadcast_multiplication(do_jit):
|
|
168
|
+
"""The pattern furax needs: q1[None, :] * q2[:, None] broadcasts like raw arrays would."""
|
|
169
|
+
|
|
170
|
+
def func(q1, q2):
|
|
171
|
+
return q1[None, :] * q2[:, None]
|
|
172
|
+
|
|
173
|
+
if do_jit:
|
|
174
|
+
func = jax.jit(func)
|
|
175
|
+
|
|
176
|
+
q1 = Quaternion.random(jax.random.key(0), shape=(5,))
|
|
177
|
+
q2 = Quaternion.random(jax.random.key(1), shape=(3,))
|
|
178
|
+
result = func(q1, q2)
|
|
179
|
+
assert result.shape == (3, 5)
|
|
180
|
+
|
|
181
|
+
# Cross-check against the equivalent reshape-based broadcast (the old workaround).
|
|
182
|
+
expected = q1.reshape((1, 5)) * q2.reshape((3, 1))
|
|
183
|
+
assert jnp.allclose(result.wxyz, expected.wxyz)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_getitem_preserves_dtype():
|
|
187
|
+
q = Quaternion.from_array(jnp.ones((4, 4), dtype=jnp.float16))
|
|
188
|
+
assert q[1:3].dtype == jnp.float16
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_getitem_pytree_leaf_indexing_under_vmap():
|
|
192
|
+
"""Quaternion indexing composes with jax.vmap, exercising the pytree registration."""
|
|
193
|
+
q = _arange_quat(3, 4)
|
|
194
|
+
|
|
195
|
+
def first_row(qi: Quaternion) -> Quaternion:
|
|
196
|
+
return qi[0]
|
|
197
|
+
|
|
198
|
+
result = jax.vmap(first_row)(q)
|
|
199
|
+
assert result.shape == (3,)
|
|
200
|
+
assert jnp.allclose(result.wxyz, q.wxyz[:, 0])
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_getitem_boolean_mask_matches_numpy_reference():
|
|
204
|
+
rng = np.random.default_rng(0)
|
|
205
|
+
mask = rng.random(7) > 0.5
|
|
206
|
+
q = _arange_quat(7)
|
|
207
|
+
result = q[jnp.asarray(mask)]
|
|
208
|
+
assert jnp.allclose(result.wxyz, q.wxyz[mask])
|
fastquat-1.0b1/docs/README.md
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# Documentation Build Instructions
|
|
2
|
-
|
|
3
|
-
This documentation uses **PEP 735 dependency groups** instead of separate requirements files to avoid duplication.
|
|
4
|
-
|
|
5
|
-
## Building Documentation Locally
|
|
6
|
-
|
|
7
|
-
### Using uv (recommended)
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# From the docs/ directory
|
|
11
|
-
cd docs/
|
|
12
|
-
uv run --group docs make html
|
|
13
|
-
|
|
14
|
-
# Or from the root directory
|
|
15
|
-
uv run --group docs sphinx-build docs/source docs/build/html
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### Using other tools
|
|
19
|
-
|
|
20
|
-
If you're not using uv, you can install the docs dependencies manually:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# Install the project with docs dependencies
|
|
24
|
-
pip install -e .[docs]
|
|
25
|
-
|
|
26
|
-
# Then build the docs
|
|
27
|
-
cd docs/
|
|
28
|
-
make html
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## What Changed
|
|
32
|
-
|
|
33
|
-
### ✅ Before (duplicated dependencies)
|
|
34
|
-
- Dependencies listed in both `pyproject.toml` and `docs/requirements.txt`
|
|
35
|
-
- ReadTheDocs config pointed to `docs/requirements.txt`
|
|
36
|
-
|
|
37
|
-
### ✅ After (PEP 735 dependency groups)
|
|
38
|
-
- Dependencies only in `pyproject.toml` under `[dependency-groups.docs]`
|
|
39
|
-
- ReadTheDocs config uses `extra_requirements: [docs]`
|
|
40
|
-
- No more `docs/requirements.txt` file
|
|
41
|
-
|
|
42
|
-
## PEP 735 Benefits
|
|
43
|
-
|
|
44
|
-
1. **Single source of truth** - dependencies defined once in `pyproject.toml`
|
|
45
|
-
2. **No duplication** - eliminates sync issues between files
|
|
46
|
-
3. **Modern standard** - follows PEP 735 specification
|
|
47
|
-
4. **Tool support** - works with uv, pip, and ReadTheDocs
|
|
48
|
-
|
|
49
|
-
## ReadTheDocs Configuration
|
|
50
|
-
|
|
51
|
-
The `.readthedocs.yaml` configuration now uses:
|
|
52
|
-
|
|
53
|
-
```yaml
|
|
54
|
-
python:
|
|
55
|
-
install:
|
|
56
|
-
- method: pip
|
|
57
|
-
path: .
|
|
58
|
-
extra_requirements:
|
|
59
|
-
- docs
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
This automatically installs the project with the `docs` dependency group.
|
|
63
|
-
|
|
64
|
-
## Available Commands
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
# Build HTML documentation
|
|
68
|
-
uv run --group docs make html
|
|
69
|
-
|
|
70
|
-
# Clean and rebuild
|
|
71
|
-
uv run --group docs make clean html
|
|
72
|
-
|
|
73
|
-
# Auto-rebuild on changes (requires sphinx-autobuild)
|
|
74
|
-
uv run --group docs sphinx-autobuild source build/html
|
|
75
|
-
|
|
76
|
-
# View available make targets
|
|
77
|
-
uv run --group docs make help
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Dependencies in the `docs` Group
|
|
81
|
-
|
|
82
|
-
The documentation dependencies are defined in `pyproject.toml`:
|
|
83
|
-
|
|
84
|
-
```toml
|
|
85
|
-
[dependency-groups]
|
|
86
|
-
docs = [
|
|
87
|
-
"sphinx>=7.0.0",
|
|
88
|
-
"sphinx-rtd-theme>=1.3.0",
|
|
89
|
-
"myst-parser>=2.0.0",
|
|
90
|
-
"nbsphinx>=0.9.0",
|
|
91
|
-
"jupyter>=1.0.0",
|
|
92
|
-
"matplotlib>=3.7.0",
|
|
93
|
-
"numpy>=1.24.0",
|
|
94
|
-
"pillow>=9.0.0",
|
|
95
|
-
]
|
|
96
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|