haliax 1.4.dev411__tar.gz → 1.4.dev412__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.
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.agents/projects/api_parity.md +13 -18
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.playbooks/wrap-non-named.md +8 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/PKG-INFO +1 -1
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/api.md +43 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/__about__.py +1 -1
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/__init__.py +22 -0
- haliax-1.4.dev412/src/haliax/fft.py +365 -0
- haliax-1.4.dev412/tests/test_fft.py +81 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.coveragerc +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.flake8 +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.gitignore +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.playbooks/add-types.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/AGENTS.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/AUTHORS.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/CONTRIBUTORS.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/LICENSE +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/README.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/css/material.css +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/faq.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/fp8.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/index.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/indexing.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/matmul.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/nn.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/partitioning.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/primer.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/rearrange.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/requirements.txt +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/scan.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/state-dict.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/tutorial.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/typing.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/docs/vmap.md +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/etc/license_header.txt +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/mkdocs.yml +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/pyproject.toml +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/scan.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/state_dict.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/core.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/field.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/haxtyping.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/jax_utils.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/embedding.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/linear.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/normalization.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/nn/scan.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/random.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/types.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/util.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/core_test.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_attention.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_axis.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_bitwise_ops.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_conv.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_debug.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_dot.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_dtype_typing.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_field.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_hof.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_int8.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_moe_linear.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_namedarray_typing.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_nan_reductions.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_nn.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_ops.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_pool.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_random.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_scan.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_scatter_gather.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_utils.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/tests/test_visualize_sharding.py +0 -0
- {haliax-1.4.dev411 → haliax-1.4.dev412}/uv.lock +0 -0
|
@@ -161,24 +161,19 @@ APIs that don't translate well to named tensors are intentionally omitted here.
|
|
|
161
161
|
- [ ] `vstack`
|
|
162
162
|
|
|
163
163
|
## fft
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
- [
|
|
168
|
-
- [
|
|
169
|
-
- [
|
|
170
|
-
- [
|
|
171
|
-
- [
|
|
172
|
-
- [
|
|
173
|
-
- [
|
|
174
|
-
- [
|
|
175
|
-
- [
|
|
176
|
-
- [
|
|
177
|
-
- [ ] `irfftn`
|
|
178
|
-
- [ ] `rfft`
|
|
179
|
-
- [ ] `rfft2`
|
|
180
|
-
- [ ] `rfftfreq`
|
|
181
|
-
- [ ] `rfftn`
|
|
164
|
+
Multi-dimensional transforms like `fftn` or `rfft2` are handled by passing a
|
|
165
|
+
mapping of axes to :func:`haliax.fft` and friends.
|
|
166
|
+
|
|
167
|
+
- [x] `fft`
|
|
168
|
+
- [x] `fftfreq`
|
|
169
|
+
- [x] `fftshift`
|
|
170
|
+
- [x] `hfft`
|
|
171
|
+
- [x] `ifft`
|
|
172
|
+
- [x] `ifftshift`
|
|
173
|
+
- [x] `ihfft`
|
|
174
|
+
- [x] `irfft`
|
|
175
|
+
- [x] `rfft`
|
|
176
|
+
- [x] `rfftfreq`
|
|
182
177
|
|
|
183
178
|
## linalg
|
|
184
179
|
- [ ] `cholesky`
|
|
@@ -45,6 +45,14 @@ def sum(a, axis=None):
|
|
|
45
45
|
## Harder Cases
|
|
46
46
|
Some functions need bespoke handling. For example `jnp.unique` returns several arrays and may change shape unpredictably. There is no generic helper, so you will need to manually map between `NamedArray` axes and the outputs. Use the lower level utilities in `haliax.wrap` for broadcasting and axis lookup.
|
|
47
47
|
|
|
48
|
+
## Axis-aware functions
|
|
49
|
+
For JAX functions that accept an ``axis`` or ``axes`` argument, prefer a single
|
|
50
|
+
``axis`` parameter of type :class:`haliax.AxisSelection` that also accepts an
|
|
51
|
+
ordered mapping of axes to sizes. A mapping dispatches to the corresponding
|
|
52
|
+
``n``‑dimensional JAX primitive (e.g. :func:`jax.numpy.fft.fftn`) and allows
|
|
53
|
+
resizing the transformed axes by specifying integer lengths or ``Axis``
|
|
54
|
+
instances.
|
|
55
|
+
|
|
48
56
|
## Testing
|
|
49
57
|
Add tests to ensure that named and unnamed calls produce the same results and that axis names are preserved or removed correctly.
|
|
50
58
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev412
|
|
4
4
|
Summary: Named Tensors for Legible Deep Learning in JAX
|
|
5
5
|
Project-URL: Homepage, https://github.com/stanford-crfm/haliax
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/stanford-crfm/haliax/issues/
|
|
@@ -289,6 +289,49 @@ These are all more or less directly from JAX's NumPy API.
|
|
|
289
289
|
::: haliax.triu
|
|
290
290
|
::: haliax.where
|
|
291
291
|
|
|
292
|
+
### FFT
|
|
293
|
+
|
|
294
|
+
All FFT helpers accept an ``axis`` argument which may be a single axis, its
|
|
295
|
+
name, or an ordered mapping from axes to output sizes. Passing a mapping
|
|
296
|
+
dispatches to the ``n``‑dimensional variants in :mod:`jax.numpy.fft`.
|
|
297
|
+
|
|
298
|
+
For example::
|
|
299
|
+
|
|
300
|
+
import jax.numpy as jnp
|
|
301
|
+
import haliax as hax
|
|
302
|
+
|
|
303
|
+
T = hax.Axis("time", 8)
|
|
304
|
+
signal = hax.arange(T, dtype=jnp.float32)
|
|
305
|
+
|
|
306
|
+
# operate along a single axis specified by name
|
|
307
|
+
hax.fft(signal, axis="time")
|
|
308
|
+
|
|
309
|
+
# resize by passing an Axis object
|
|
310
|
+
hax.fft(signal, axis=hax.Axis("time", 16))
|
|
311
|
+
|
|
312
|
+
X, Y = hax.make_axes(X=4, Y=6)
|
|
313
|
+
image = hax.arange((X, Y), dtype=jnp.float32)
|
|
314
|
+
|
|
315
|
+
# transform across several axes in order by passing a sequence
|
|
316
|
+
hax.fft(image, axis=("X", "Y"))
|
|
317
|
+
|
|
318
|
+
# selectively resize axes by providing a mapping
|
|
319
|
+
hax.fft(image, axis={"X": None, "Y": hax.Axis("Y", 10)})
|
|
320
|
+
|
|
321
|
+
# mappings can cover just a subset of axes when only partial resizing is needed
|
|
322
|
+
hax.fft(image, axis={"Y": 10})
|
|
323
|
+
|
|
324
|
+
::: haliax.fft
|
|
325
|
+
::: haliax.ifft
|
|
326
|
+
::: haliax.hfft
|
|
327
|
+
::: haliax.ihfft
|
|
328
|
+
::: haliax.rfft
|
|
329
|
+
::: haliax.irfft
|
|
330
|
+
::: haliax.fftfreq
|
|
331
|
+
::: haliax.rfftfreq
|
|
332
|
+
::: haliax.fftshift
|
|
333
|
+
::: haliax.ifftshift
|
|
334
|
+
|
|
292
335
|
|
|
293
336
|
|
|
294
337
|
## Named Array Reference
|
|
@@ -96,6 +96,18 @@ from .ops import (
|
|
|
96
96
|
bincount,
|
|
97
97
|
where,
|
|
98
98
|
)
|
|
99
|
+
from .fft import (
|
|
100
|
+
fft,
|
|
101
|
+
fftfreq,
|
|
102
|
+
fftshift,
|
|
103
|
+
hfft,
|
|
104
|
+
ifft,
|
|
105
|
+
ifftshift,
|
|
106
|
+
ihfft,
|
|
107
|
+
irfft,
|
|
108
|
+
rfft,
|
|
109
|
+
rfftfreq,
|
|
110
|
+
)
|
|
99
111
|
from .partitioning import auto_sharded, axis_mapping, fsdp, named_jit, shard, shard_with_axis_mapping
|
|
100
112
|
from .specialized_fns import top_k
|
|
101
113
|
from .types import Scalar
|
|
@@ -1214,6 +1226,16 @@ __all__ = [
|
|
|
1214
1226
|
"clip",
|
|
1215
1227
|
"tril",
|
|
1216
1228
|
"triu",
|
|
1229
|
+
"fft",
|
|
1230
|
+
"ifft",
|
|
1231
|
+
"hfft",
|
|
1232
|
+
"ihfft",
|
|
1233
|
+
"rfft",
|
|
1234
|
+
"irfft",
|
|
1235
|
+
"fftfreq",
|
|
1236
|
+
"rfftfreq",
|
|
1237
|
+
"fftshift",
|
|
1238
|
+
"ifftshift",
|
|
1217
1239
|
"add",
|
|
1218
1240
|
"arctan2",
|
|
1219
1241
|
"bitwise_and",
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"""Named wrappers around :mod:`jax.numpy.fft`.
|
|
2
|
+
|
|
3
|
+
These functions mirror the behaviour of their :mod:`jax.numpy.fft` counterparts
|
|
4
|
+
while accepting named axes. Instead of separate ``fftn``/``fft2`` variants we
|
|
5
|
+
provide a single ``fft`` family of functions whose ``axis`` argument controls
|
|
6
|
+
which axes are transformed.
|
|
7
|
+
|
|
8
|
+
The ``axis`` parameter can be one of:
|
|
9
|
+
|
|
10
|
+
* ``None`` – operate on the last axis.
|
|
11
|
+
* ``str`` – name of an existing axis in the input.
|
|
12
|
+
* :class:`~haliax.Axis` – specifies both the axis to transform (by name) and the
|
|
13
|
+
desired FFT length. The output axis is replaced by the provided ``Axis``.
|
|
14
|
+
* ``dict`` – mapping from axis selectors (names or ``Axis`` objects) to optional
|
|
15
|
+
sizes. A value of ``None`` uses the existing axis length. The mapping order
|
|
16
|
+
determines the order of transforms and dispatches to the ``n``‑dimensional
|
|
17
|
+
variants in :mod:`jax.numpy.fft`.
|
|
18
|
+
|
|
19
|
+
Example
|
|
20
|
+
-------
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
X, Y = hax.make_axes(X=4, Y=6)
|
|
24
|
+
arr = hax.arange((X, Y))
|
|
25
|
+
|
|
26
|
+
# 1D transform along ``Y``
|
|
27
|
+
hax.fft(arr, axis="Y")
|
|
28
|
+
|
|
29
|
+
# 2D transform across both axes
|
|
30
|
+
hax.fft(arr, axis={"X": None, "Y": None})
|
|
31
|
+
|
|
32
|
+
# Resize the ``Y`` axis before transforming
|
|
33
|
+
hax.fft(arr, axis={"Y": Axis("Y", 8)})
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from typing import Mapping, MutableSequence, Sequence
|
|
40
|
+
|
|
41
|
+
import jax.numpy.fft as jfft
|
|
42
|
+
|
|
43
|
+
from .axis import Axis, AxisSelector, AxisSelection
|
|
44
|
+
from .core import NamedArray
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
AxisSizeLike = int | Axis | None
|
|
48
|
+
AxisMapping = Mapping[AxisSelector, AxisSizeLike]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _single_axis(a: NamedArray, axis: AxisSelector | None):
|
|
52
|
+
if axis is None:
|
|
53
|
+
idx = a.ndim - 1
|
|
54
|
+
ax = a.axes[idx]
|
|
55
|
+
n = None
|
|
56
|
+
elif isinstance(axis, Axis):
|
|
57
|
+
idx = a.axis_indices(axis.name)
|
|
58
|
+
if idx is None:
|
|
59
|
+
raise ValueError(f"Axis {axis} not found in {a.axes}")
|
|
60
|
+
ax = axis
|
|
61
|
+
n = axis.size
|
|
62
|
+
else:
|
|
63
|
+
idx = a.axis_indices(axis)
|
|
64
|
+
if idx is None:
|
|
65
|
+
raise ValueError(f"Axis {axis} not found in {a.axes}")
|
|
66
|
+
ax = a.axes[idx]
|
|
67
|
+
n = None
|
|
68
|
+
return idx, ax, n
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _multi_axis(a: NamedArray, axis: AxisMapping):
|
|
72
|
+
axes_idx: MutableSequence[int] = []
|
|
73
|
+
sizes: MutableSequence[int] = []
|
|
74
|
+
new_axes = list(a.axes)
|
|
75
|
+
for key, val in axis.items():
|
|
76
|
+
idx = a.axis_indices(key)
|
|
77
|
+
if idx is None:
|
|
78
|
+
raise ValueError(f"Axis {key} not found in {a.axes}")
|
|
79
|
+
ax = a.axes[idx]
|
|
80
|
+
if isinstance(val, Axis):
|
|
81
|
+
size = val.size
|
|
82
|
+
new_axes[idx] = val
|
|
83
|
+
elif val is None:
|
|
84
|
+
size = ax.size
|
|
85
|
+
else:
|
|
86
|
+
size = int(val)
|
|
87
|
+
new_axes[idx] = ax.resize(size)
|
|
88
|
+
axes_idx.append(idx)
|
|
89
|
+
sizes.append(size)
|
|
90
|
+
return list(axes_idx), list(sizes), new_axes
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def fft(
|
|
94
|
+
a: NamedArray,
|
|
95
|
+
axis: AxisSelector | Sequence[AxisSelector] | AxisMapping | None = None,
|
|
96
|
+
norm: str | None = None,
|
|
97
|
+
) -> NamedArray:
|
|
98
|
+
"""Named version of :func:`jax.numpy.fft.fft`.
|
|
99
|
+
|
|
100
|
+
See module level documentation for the behaviour of the ``axis`` argument.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
if isinstance(axis, Mapping):
|
|
104
|
+
axes_idx, sizes, new_axes = _multi_axis(a, axis)
|
|
105
|
+
out = jfft.fftn(a.array, s=tuple(sizes), axes=tuple(axes_idx), norm=norm)
|
|
106
|
+
return NamedArray(out, tuple(new_axes))
|
|
107
|
+
elif isinstance(axis, Sequence) and not isinstance(axis, (str, Axis)):
|
|
108
|
+
axes_idx, sizes, new_axes = _multi_axis(a, {ax: None for ax in axis})
|
|
109
|
+
out = jfft.fftn(a.array, s=tuple(sizes), axes=tuple(axes_idx), norm=norm)
|
|
110
|
+
return NamedArray(out, tuple(new_axes))
|
|
111
|
+
else:
|
|
112
|
+
idx, new_axis, n = _single_axis(a, axis) # type: ignore[arg-type]
|
|
113
|
+
out = jfft.fft(a.array, n=n, axis=idx, norm=norm)
|
|
114
|
+
axes = list(a.axes)
|
|
115
|
+
axes[idx] = new_axis
|
|
116
|
+
return NamedArray(out, tuple(axes))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def ifft(
|
|
120
|
+
a: NamedArray,
|
|
121
|
+
axis: AxisSelector | Sequence[AxisSelector] | AxisMapping | None = None,
|
|
122
|
+
norm: str | None = None,
|
|
123
|
+
) -> NamedArray:
|
|
124
|
+
"""Named version of :func:`jax.numpy.fft.ifft`."""
|
|
125
|
+
|
|
126
|
+
if isinstance(axis, Mapping):
|
|
127
|
+
axes_idx, sizes, new_axes = _multi_axis(a, axis)
|
|
128
|
+
out = jfft.ifftn(a.array, s=tuple(sizes), axes=tuple(axes_idx), norm=norm)
|
|
129
|
+
return NamedArray(out, tuple(new_axes))
|
|
130
|
+
elif isinstance(axis, Sequence) and not isinstance(axis, (str, Axis)):
|
|
131
|
+
axes_idx, sizes, new_axes = _multi_axis(a, {ax: None for ax in axis})
|
|
132
|
+
out = jfft.ifftn(a.array, s=tuple(sizes), axes=tuple(axes_idx), norm=norm)
|
|
133
|
+
return NamedArray(out, tuple(new_axes))
|
|
134
|
+
else:
|
|
135
|
+
idx, new_axis, n = _single_axis(a, axis) # type: ignore[arg-type]
|
|
136
|
+
out = jfft.ifft(a.array, n=n, axis=idx, norm=norm)
|
|
137
|
+
axes = list(a.axes)
|
|
138
|
+
axes[idx] = new_axis
|
|
139
|
+
return NamedArray(out, tuple(axes))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def rfft(
|
|
143
|
+
a: NamedArray,
|
|
144
|
+
axis: AxisSelector | Sequence[AxisSelector] | AxisMapping | None = None,
|
|
145
|
+
norm: str | None = None,
|
|
146
|
+
) -> NamedArray:
|
|
147
|
+
"""Named version of :func:`jax.numpy.fft.rfft`."""
|
|
148
|
+
|
|
149
|
+
if isinstance(axis, Mapping):
|
|
150
|
+
axes_idx, sizes, new_axes = _multi_axis(a, axis)
|
|
151
|
+
out = jfft.rfftn(a.array, s=tuple(sizes), axes=tuple(axes_idx), norm=norm)
|
|
152
|
+
last_idx = axes_idx[-1]
|
|
153
|
+
last_in = sizes[-1]
|
|
154
|
+
new_axes[last_idx] = new_axes[last_idx].resize(last_in // 2 + 1)
|
|
155
|
+
return NamedArray(out, tuple(new_axes))
|
|
156
|
+
elif isinstance(axis, Sequence) and not isinstance(axis, (str, Axis)):
|
|
157
|
+
axes_idx, sizes, new_axes = _multi_axis(a, {ax: None for ax in axis})
|
|
158
|
+
out = jfft.rfftn(a.array, s=tuple(sizes), axes=tuple(axes_idx), norm=norm)
|
|
159
|
+
last_idx = axes_idx[-1]
|
|
160
|
+
last_in = sizes[-1]
|
|
161
|
+
new_axes[last_idx] = new_axes[last_idx].resize(last_in // 2 + 1)
|
|
162
|
+
return NamedArray(out, tuple(new_axes))
|
|
163
|
+
else:
|
|
164
|
+
idx, ax, n = _single_axis(a, axis) # type: ignore[arg-type]
|
|
165
|
+
out = jfft.rfft(a.array, n=n, axis=idx, norm=norm)
|
|
166
|
+
length = n if n is not None else ax.size
|
|
167
|
+
new_axis = ax.resize(length // 2 + 1)
|
|
168
|
+
axes = list(a.axes)
|
|
169
|
+
axes[idx] = new_axis
|
|
170
|
+
return NamedArray(out, tuple(axes))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _single_axis_irfft(a: NamedArray, axis: AxisSelector | None):
|
|
174
|
+
if axis is None:
|
|
175
|
+
idx = a.ndim - 1
|
|
176
|
+
in_ax = a.axes[idx]
|
|
177
|
+
length = (in_ax.size - 1) * 2
|
|
178
|
+
out_ax = in_ax.resize(length)
|
|
179
|
+
n = None
|
|
180
|
+
elif isinstance(axis, Axis):
|
|
181
|
+
idx = a.axis_indices(axis.name)
|
|
182
|
+
if idx is None:
|
|
183
|
+
raise ValueError(f"Axis {axis} not found in {a.axes}")
|
|
184
|
+
out_ax = axis
|
|
185
|
+
n = axis.size
|
|
186
|
+
else:
|
|
187
|
+
idx = a.axis_indices(axis)
|
|
188
|
+
if idx is None:
|
|
189
|
+
raise ValueError(f"Axis {axis} not found in {a.axes}")
|
|
190
|
+
in_ax = a.axes[idx]
|
|
191
|
+
length = (in_ax.size - 1) * 2
|
|
192
|
+
out_ax = in_ax.resize(length)
|
|
193
|
+
n = None
|
|
194
|
+
return idx, out_ax, n
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _multi_axis_irfft(a: NamedArray, axis: AxisMapping):
|
|
198
|
+
axes_idx: MutableSequence[int] = []
|
|
199
|
+
sizes: MutableSequence[int] = []
|
|
200
|
+
new_axes = list(a.axes)
|
|
201
|
+
items = list(axis.items())
|
|
202
|
+
for i, (key, val) in enumerate(items):
|
|
203
|
+
idx = a.axis_indices(key)
|
|
204
|
+
if idx is None:
|
|
205
|
+
raise ValueError(f"Axis {key} not found in {a.axes}")
|
|
206
|
+
ax = a.axes[idx]
|
|
207
|
+
if isinstance(val, Axis):
|
|
208
|
+
size = val.size
|
|
209
|
+
new_axes[idx] = val
|
|
210
|
+
elif val is None:
|
|
211
|
+
if i == len(items) - 1:
|
|
212
|
+
size = (ax.size - 1) * 2
|
|
213
|
+
else:
|
|
214
|
+
size = ax.size
|
|
215
|
+
new_axes[idx] = ax.resize(size)
|
|
216
|
+
else:
|
|
217
|
+
size = int(val)
|
|
218
|
+
new_axes[idx] = ax.resize(size)
|
|
219
|
+
axes_idx.append(idx)
|
|
220
|
+
sizes.append(size)
|
|
221
|
+
return list(axes_idx), list(sizes), new_axes
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def irfft(
|
|
225
|
+
a: NamedArray,
|
|
226
|
+
axis: AxisSelector | Sequence[AxisSelector] | AxisMapping | None = None,
|
|
227
|
+
norm: str | None = None,
|
|
228
|
+
) -> NamedArray:
|
|
229
|
+
"""Named version of :func:`jax.numpy.fft.irfft`."""
|
|
230
|
+
|
|
231
|
+
if isinstance(axis, Mapping):
|
|
232
|
+
axes_idx, sizes, new_axes = _multi_axis_irfft(a, axis)
|
|
233
|
+
out = jfft.irfftn(a.array, s=tuple(sizes), axes=tuple(axes_idx), norm=norm)
|
|
234
|
+
return NamedArray(out, tuple(new_axes))
|
|
235
|
+
elif isinstance(axis, Sequence) and not isinstance(axis, (str, Axis)):
|
|
236
|
+
axes_idx, sizes, new_axes = _multi_axis_irfft(a, {ax: None for ax in axis})
|
|
237
|
+
out = jfft.irfftn(a.array, s=tuple(sizes), axes=tuple(axes_idx), norm=norm)
|
|
238
|
+
return NamedArray(out, tuple(new_axes))
|
|
239
|
+
else:
|
|
240
|
+
idx, out_ax, n = _single_axis_irfft(a, axis) # type: ignore[arg-type]
|
|
241
|
+
out = jfft.irfft(a.array, n=n, axis=idx, norm=norm)
|
|
242
|
+
axes = list(a.axes)
|
|
243
|
+
axes[idx] = out_ax
|
|
244
|
+
return NamedArray(out, tuple(axes))
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def hfft(
|
|
248
|
+
a: NamedArray,
|
|
249
|
+
axis: AxisSelector | Sequence[AxisSelector] | AxisMapping | None = None,
|
|
250
|
+
norm: str | None = None,
|
|
251
|
+
) -> NamedArray:
|
|
252
|
+
"""Named version of :func:`jax.numpy.fft.hfft`.
|
|
253
|
+
|
|
254
|
+
Only a single axis is supported; passing a dictionary with more than one
|
|
255
|
+
entry will raise an error.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
if isinstance(axis, Mapping):
|
|
259
|
+
if len(axis) != 1:
|
|
260
|
+
raise ValueError("hfft only supports a single axis")
|
|
261
|
+
key, val = next(iter(axis.items()))
|
|
262
|
+
if isinstance(val, Axis):
|
|
263
|
+
axis = val
|
|
264
|
+
elif val is None:
|
|
265
|
+
axis = key
|
|
266
|
+
else:
|
|
267
|
+
name = key.name if isinstance(key, Axis) else key
|
|
268
|
+
axis = Axis(name, int(val))
|
|
269
|
+
|
|
270
|
+
idx, out_ax, n = _single_axis_irfft(a, axis) # type: ignore[arg-type]
|
|
271
|
+
out = jfft.hfft(a.array, n=n, axis=idx, norm=norm)
|
|
272
|
+
axes = list(a.axes)
|
|
273
|
+
axes[idx] = out_ax
|
|
274
|
+
return NamedArray(out, tuple(axes))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def ihfft(
|
|
278
|
+
a: NamedArray,
|
|
279
|
+
axis: AxisSelector | Sequence[AxisSelector] | AxisMapping | None = None,
|
|
280
|
+
norm: str | None = None,
|
|
281
|
+
) -> NamedArray:
|
|
282
|
+
"""Named version of :func:`jax.numpy.fft.ihfft`.
|
|
283
|
+
|
|
284
|
+
Only a single axis is supported; passing a dictionary with more than one
|
|
285
|
+
entry will raise an error.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
if isinstance(axis, Mapping):
|
|
289
|
+
if len(axis) != 1:
|
|
290
|
+
raise ValueError("ihfft only supports a single axis")
|
|
291
|
+
key, val = next(iter(axis.items()))
|
|
292
|
+
if isinstance(val, Axis):
|
|
293
|
+
axis = val
|
|
294
|
+
elif val is None:
|
|
295
|
+
axis = key
|
|
296
|
+
else:
|
|
297
|
+
name = key.name if isinstance(key, Axis) else key
|
|
298
|
+
axis = Axis(name, int(val))
|
|
299
|
+
|
|
300
|
+
idx, ax, n = _single_axis(a, axis) # type: ignore[arg-type]
|
|
301
|
+
out = jfft.ihfft(a.array, n=n, axis=idx, norm=norm)
|
|
302
|
+
length = n if n is not None else ax.size // 2 + 1
|
|
303
|
+
new_axis = ax.resize(length)
|
|
304
|
+
axes = list(a.axes)
|
|
305
|
+
axes[idx] = new_axis
|
|
306
|
+
return NamedArray(out, tuple(axes))
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def fftshift(x: NamedArray, axes: AxisSelection | None = None) -> NamedArray:
|
|
310
|
+
"""Named version of :func:`jax.numpy.fft.fftshift`."""
|
|
311
|
+
|
|
312
|
+
if axes is None:
|
|
313
|
+
out = jfft.fftshift(x.array)
|
|
314
|
+
else:
|
|
315
|
+
idxs = x.axis_indices(axes)
|
|
316
|
+
if isinstance(idxs, tuple):
|
|
317
|
+
if any(i is None for i in idxs):
|
|
318
|
+
raise ValueError(f"Axis {axes} not found in {x.axes}")
|
|
319
|
+
elif idxs is None:
|
|
320
|
+
raise ValueError(f"Axis {axes} not found in {x.axes}")
|
|
321
|
+
out = jfft.fftshift(x.array, axes=idxs)
|
|
322
|
+
return NamedArray(out, x.axes)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def ifftshift(x: NamedArray, axes: AxisSelection | None = None) -> NamedArray:
|
|
326
|
+
"""Named version of :func:`jax.numpy.fft.ifftshift`."""
|
|
327
|
+
|
|
328
|
+
if axes is None:
|
|
329
|
+
out = jfft.ifftshift(x.array)
|
|
330
|
+
else:
|
|
331
|
+
idxs = x.axis_indices(axes)
|
|
332
|
+
if isinstance(idxs, tuple):
|
|
333
|
+
if any(i is None for i in idxs):
|
|
334
|
+
raise ValueError(f"Axis {axes} not found in {x.axes}")
|
|
335
|
+
elif idxs is None:
|
|
336
|
+
raise ValueError(f"Axis {axes} not found in {x.axes}")
|
|
337
|
+
out = jfft.ifftshift(x.array, axes=idxs)
|
|
338
|
+
return NamedArray(out, x.axes)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def fftfreq(axis: Axis, d: float = 1.0) -> NamedArray:
|
|
342
|
+
"""Named version of :func:`jax.numpy.fft.fftfreq`."""
|
|
343
|
+
|
|
344
|
+
return NamedArray(jfft.fftfreq(axis.size, d), (axis,))
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def rfftfreq(axis: Axis, d: float = 1.0) -> NamedArray:
|
|
348
|
+
"""Named version of :func:`jax.numpy.fft.rfftfreq`."""
|
|
349
|
+
|
|
350
|
+
new_axis = axis.resize(axis.size // 2 + 1)
|
|
351
|
+
return NamedArray(jfft.rfftfreq(axis.size, d), (new_axis,))
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
__all__ = [
|
|
355
|
+
"fft",
|
|
356
|
+
"ifft",
|
|
357
|
+
"rfft",
|
|
358
|
+
"irfft",
|
|
359
|
+
"hfft",
|
|
360
|
+
"ihfft",
|
|
361
|
+
"fftfreq",
|
|
362
|
+
"rfftfreq",
|
|
363
|
+
"fftshift",
|
|
364
|
+
"ifftshift",
|
|
365
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import jax.numpy as jnp
|
|
2
|
+
import jax.numpy.fft as jfft
|
|
3
|
+
|
|
4
|
+
import haliax as hax
|
|
5
|
+
from haliax import Axis
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_fft_axis_options():
|
|
9
|
+
N = Axis("n", 8)
|
|
10
|
+
x = hax.arange(N, dtype=jnp.float32)
|
|
11
|
+
|
|
12
|
+
# string axis
|
|
13
|
+
assert jnp.allclose(hax.fft(x, axis="n").array, jfft.fft(x.array))
|
|
14
|
+
|
|
15
|
+
# Axis object with resize
|
|
16
|
+
N2 = Axis("n", 16)
|
|
17
|
+
f = hax.fft(x, axis=N2)
|
|
18
|
+
assert f.axes[0] == N2
|
|
19
|
+
assert jnp.allclose(f.array, jfft.fft(x.array, n=16))
|
|
20
|
+
|
|
21
|
+
r = hax.rfft(x)
|
|
22
|
+
assert r.axes[0].size == 5
|
|
23
|
+
assert jnp.allclose(r.array, jfft.rfft(x.array))
|
|
24
|
+
|
|
25
|
+
ir = hax.irfft(r)
|
|
26
|
+
assert ir.axes[0].size == 8
|
|
27
|
+
assert jnp.allclose(ir.array, jfft.irfft(jfft.rfft(x.array)))
|
|
28
|
+
|
|
29
|
+
h = hax.hfft(r)
|
|
30
|
+
assert h.axes[0].size == 8
|
|
31
|
+
assert jnp.allclose(h.array, jfft.hfft(jfft.rfft(x.array)))
|
|
32
|
+
|
|
33
|
+
ih = hax.ihfft(x)
|
|
34
|
+
assert ih.axes[0].size == 5
|
|
35
|
+
assert jnp.allclose(ih.array, jfft.ihfft(x.array))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_fft_freq_and_shift():
|
|
39
|
+
N = Axis("n", 8)
|
|
40
|
+
x = hax.arange(N)
|
|
41
|
+
|
|
42
|
+
f = hax.fftfreq(N)
|
|
43
|
+
assert f.axes == (N,)
|
|
44
|
+
assert jnp.allclose(f.array, jfft.fftfreq(8))
|
|
45
|
+
|
|
46
|
+
rf = hax.rfftfreq(N)
|
|
47
|
+
assert rf.axes[0].size == 5
|
|
48
|
+
assert jnp.allclose(rf.array, jfft.rfftfreq(8))
|
|
49
|
+
|
|
50
|
+
shifted = hax.fftshift(x)
|
|
51
|
+
assert jnp.allclose(shifted.array, jfft.fftshift(x.array))
|
|
52
|
+
unshifted = hax.ifftshift(shifted)
|
|
53
|
+
assert jnp.allclose(unshifted.array, x.array)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_fft_multi_axis():
|
|
57
|
+
X = Axis("x", 4)
|
|
58
|
+
Y = Axis("y", 6)
|
|
59
|
+
Z = Axis("z", 8)
|
|
60
|
+
arr = hax.arange((X, Y, Z), dtype=jnp.float32)
|
|
61
|
+
|
|
62
|
+
f = hax.fft(arr, axis={"y": None, "z": None})
|
|
63
|
+
assert jnp.allclose(f.array, jfft.fftn(arr.array, axes=(1, 2)))
|
|
64
|
+
|
|
65
|
+
f_seq = hax.fft(arr, axis=("y", "z"))
|
|
66
|
+
assert jnp.allclose(f_seq.array, jfft.fftn(arr.array, axes=(1, 2)))
|
|
67
|
+
assert f_seq.axes == f.axes
|
|
68
|
+
|
|
69
|
+
rf = hax.rfft(arr, axis={"y": None, "z": None})
|
|
70
|
+
assert rf.axes[2].size == 5
|
|
71
|
+
assert jnp.allclose(rf.array, jfft.rfftn(arr.array, axes=(1, 2)))
|
|
72
|
+
|
|
73
|
+
irf = hax.irfft(rf, axis={"y": None, "z": Z})
|
|
74
|
+
assert jnp.allclose(
|
|
75
|
+
irf.array, jfft.irfftn(jfft.rfftn(arr.array, axes=(1, 2)), s=(Y.size, Z.size), axes=(1, 2))
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# resizing via dict values
|
|
79
|
+
f2 = hax.fft(arr, axis={"y": 4, "z": None})
|
|
80
|
+
assert f2.axes[1].size == 4
|
|
81
|
+
assert jnp.allclose(f2.array, jfft.fftn(arr.array, s=(4, 8), axes=(1, 2)))
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|