coordax 0.2.1__tar.gz → 0.2.2__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.
- {coordax-0.2.1 → coordax-0.2.2}/PKG-INFO +1 -1
- {coordax-0.2.1 → coordax-0.2.2}/coordax/__init__.py +1 -1
- {coordax-0.2.1 → coordax-0.2.2}/coordax/coordinate_systems.py +33 -17
- coordax-0.2.2/coordax/experimental.py +22 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax/fields.py +48 -37
- {coordax-0.2.1 → coordax-0.2.2}/coordax/named_axes.py +32 -32
- {coordax-0.2.1 → coordax-0.2.2}/coordax/named_axes_test.py +6 -6
- {coordax-0.2.1 → coordax-0.2.2}/coordax/ndarrays.py +21 -7
- {coordax-0.2.1 → coordax-0.2.2}/coordax/testing.py +3 -3
- {coordax-0.2.1 → coordax-0.2.2}/coordax.egg-info/PKG-INFO +1 -1
- {coordax-0.2.1 → coordax-0.2.2}/coordax.egg-info/SOURCES.txt +1 -0
- {coordax-0.2.1 → coordax-0.2.2}/pyproject.toml +1 -1
- {coordax-0.2.1 → coordax-0.2.2}/LICENSE +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/README.md +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax/coordinate_systems_test.py +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax/coords.py +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax/doctest_test.py +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax/fields_test.py +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax/jax_datetime_integration_test.py +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax/utils.py +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax/xarray_test.py +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax.egg-info/dependency_links.txt +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax.egg-info/requires.txt +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/coordax.egg-info/top_level.txt +0 -0
- {coordax-0.2.1 → coordax-0.2.2}/setup.cfg +0 -0
|
@@ -46,7 +46,7 @@ Sequence = collections.abc.Sequence
|
|
|
46
46
|
@functools.partial(utils.export, module='coordax.coords')
|
|
47
47
|
@dataclasses.dataclass(frozen=True)
|
|
48
48
|
class NoCoordinateMatch:
|
|
49
|
-
"""For use when
|
|
49
|
+
"""For use when no Coordax coordinate matches xarray coordinate."""
|
|
50
50
|
|
|
51
51
|
reason: str
|
|
52
52
|
|
|
@@ -56,13 +56,13 @@ class Coordinate(abc.ABC):
|
|
|
56
56
|
"""Abstract class for coordinate objects.
|
|
57
57
|
|
|
58
58
|
Coordinate subclasses are expected to obey several invariants:
|
|
59
|
-
1. Dimension names may not be repeated:
|
|
60
|
-
2. All dimensions must be named:
|
|
59
|
+
1. Dimension names may not be repeated: ``len(set(dims)) == len(dims)``
|
|
60
|
+
2. All dimensions must be named: ``len(shape) == len(dims)``
|
|
61
61
|
|
|
62
62
|
Every non-abstract Coordinate subclass must be registered as a "static"
|
|
63
63
|
pytree node, e.g., by decorating the class with
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
``@jax.tree_util.register_static``. Static pytrees nodes must implement
|
|
65
|
+
``__hash__`` and ``__eq__`` according to the requirements of keys in Python
|
|
66
66
|
dictionaries. This is easiest to acheive with frozen dataclasses, but care
|
|
67
67
|
must be taken when working with np.ndarray attributes.
|
|
68
68
|
"""
|
|
@@ -73,7 +73,7 @@ class Coordinate(abc.ABC):
|
|
|
73
73
|
"""Dimension names of the coordinate.
|
|
74
74
|
|
|
75
75
|
All subclasses must return a tuple of dimension names as strings, with the
|
|
76
|
-
exception of
|
|
76
|
+
exception of ``DummyAxis``.
|
|
77
77
|
"""
|
|
78
78
|
raise NotImplementedError()
|
|
79
79
|
|
|
@@ -135,8 +135,12 @@ class Coordinate(abc.ABC):
|
|
|
135
135
|
values.
|
|
136
136
|
|
|
137
137
|
Returns:
|
|
138
|
-
A matching instance of this coordinate or
|
|
139
|
-
coordinate does not match the xarray dimensions and coordinates.
|
|
138
|
+
A matching instance of this coordinate or ``NoCoordinateMatch`` if this
|
|
139
|
+
coordinate type does not match the xarray dimensions and coordinates.
|
|
140
|
+
|
|
141
|
+
See also:
|
|
142
|
+
:func:`coordax.from_xarray`
|
|
143
|
+
:func:`coordax.coords.from_xarray`
|
|
140
144
|
"""
|
|
141
145
|
raise NotImplementedError('from_xarray not implemented')
|
|
142
146
|
|
|
@@ -513,6 +517,7 @@ class LabeledAxis(Coordinate):
|
|
|
513
517
|
@functools.partial(utils.export, module='coordax.coords')
|
|
514
518
|
def compose(*coordinates: Coordinate) -> Coordinate:
|
|
515
519
|
# pylint: disable=line-too-long
|
|
520
|
+
# fmt: off
|
|
516
521
|
"""Compose coordinates into a unified coordinate system.
|
|
517
522
|
|
|
518
523
|
Args:
|
|
@@ -528,6 +533,7 @@ def compose(*coordinates: Coordinate) -> Coordinate:
|
|
|
528
533
|
>>> cx.coords.compose(x, y)
|
|
529
534
|
CartesianProduct(coordinates=(coordax.SizedAxis('x', size=2), coordax.SizedAxis('y', size=3)))
|
|
530
535
|
"""
|
|
536
|
+
# fmt: on
|
|
531
537
|
product = CartesianProduct(coordinates)
|
|
532
538
|
match len(product.coordinates):
|
|
533
539
|
case 0:
|
|
@@ -544,7 +550,8 @@ def insert_axes(
|
|
|
544
550
|
indices_to_axes: dict[int, Coordinate],
|
|
545
551
|
) -> Coordinate:
|
|
546
552
|
# pylint: disable=line-too-long
|
|
547
|
-
|
|
553
|
+
# fmt: off
|
|
554
|
+
"""Returns ``coordinate`` with extra axes inserted at specified positions.
|
|
548
555
|
|
|
549
556
|
Args:
|
|
550
557
|
coordinate: The coordinate system to modify.
|
|
@@ -561,6 +568,7 @@ def insert_axes(
|
|
|
561
568
|
>>> cx.coords.insert_axes(x, {1: z})
|
|
562
569
|
CartesianProduct(coordinates=(coordax.SizedAxis('x', size=2), coordax.SizedAxis('z', size=4)))
|
|
563
570
|
"""
|
|
571
|
+
# fmt: on
|
|
564
572
|
indices_to_axes = indices_to_axes.copy()
|
|
565
573
|
ndim = coordinate.ndim + len(indices_to_axes)
|
|
566
574
|
normalize_idx = lambda i: i + ndim if i < 0 else i
|
|
@@ -581,7 +589,8 @@ def replace_axes(
|
|
|
581
589
|
replace_with: Coordinate,
|
|
582
590
|
) -> Coordinate:
|
|
583
591
|
# pylint: disable=line-too-long
|
|
584
|
-
|
|
592
|
+
# fmt: off
|
|
593
|
+
"""Returns ``coordinate`` with ``to_replace`` replaced by ``replace_with``.
|
|
585
594
|
|
|
586
595
|
Args:
|
|
587
596
|
coordinate: The coordinate system to modify.
|
|
@@ -589,10 +598,10 @@ def replace_axes(
|
|
|
589
598
|
replace_with: The new coordinate to insert.
|
|
590
599
|
|
|
591
600
|
Returns:
|
|
592
|
-
A new coordinate object with
|
|
601
|
+
A new coordinate object with ``to_replace`` substituted by ``replace_with``.
|
|
593
602
|
|
|
594
603
|
Raises:
|
|
595
|
-
ValueError: If
|
|
604
|
+
ValueError: If ``to_replace`` is not a contiguous part of ``coordinate``.
|
|
596
605
|
|
|
597
606
|
Examples:
|
|
598
607
|
>>> import coordax as cx
|
|
@@ -605,6 +614,7 @@ def replace_axes(
|
|
|
605
614
|
>>> cx.coords.replace_axes(xy, x, z)
|
|
606
615
|
CartesianProduct(coordinates=(coordax.SizedAxis('z', size=4), coordax.SizedAxis('y', size=3)))
|
|
607
616
|
"""
|
|
617
|
+
# fmt: on
|
|
608
618
|
if not to_replace.dims:
|
|
609
619
|
raise ValueError(f'`to_replace` must have dimensions, got {to_replace}')
|
|
610
620
|
|
|
@@ -632,10 +642,11 @@ def from_xarray(
|
|
|
632
642
|
coord_types: Sequence[type[Coordinate]] = (LabeledAxis, DummyAxis),
|
|
633
643
|
) -> Coordinate:
|
|
634
644
|
# pylint: disable=line-too-long
|
|
635
|
-
|
|
645
|
+
# fmt: off
|
|
646
|
+
"""Convert coordinates of an ``xarray.DataArray`` into ``coordax.Coordinate``.
|
|
636
647
|
|
|
637
648
|
Args:
|
|
638
|
-
data_array: xarray.DataArray whose coordinates should be converted.
|
|
649
|
+
data_array: ``xarray.DataArray`` whose coordinates should be converted.
|
|
639
650
|
coord_types: sequence of ``coordax.Coordinate`` subclasses with
|
|
640
651
|
``from_xarray`` methods defined. The first coordinate class that returns a
|
|
641
652
|
coordinate object (indicating a match) will be used. By default,
|
|
@@ -643,8 +654,9 @@ def from_xarray(
|
|
|
643
654
|
objects.
|
|
644
655
|
|
|
645
656
|
Returns:
|
|
646
|
-
A coordax.Coordinate object
|
|
647
|
-
|
|
657
|
+
A coordax.Coordinate object, constructed from any of the indicated types
|
|
658
|
+
(plus ``CartesianProduct`` and ``Scalar``), representing the coordinates of
|
|
659
|
+
the input DataArray.
|
|
648
660
|
|
|
649
661
|
Raises:
|
|
650
662
|
ValueError: if no matching coordinate is found.
|
|
@@ -654,9 +666,13 @@ def from_xarray(
|
|
|
654
666
|
>>> import xarray as xr
|
|
655
667
|
>>> import numpy as np
|
|
656
668
|
>>> da = xr.DataArray(np.zeros((2, 3)), dims=('x', 'y'), coords={'x': [1, 2]})
|
|
657
|
-
>>> cx.coords.from_xarray(da)
|
|
669
|
+
>>> cx.coords.from_xarray(da)
|
|
658
670
|
CartesianProduct(coordinates=(coordax.LabeledAxis('x', ticks=array([1, 2])), coordax.DummyAxis('y', size=3)))
|
|
671
|
+
|
|
672
|
+
See also:
|
|
673
|
+
:func:`coordax.from_xarray`
|
|
659
674
|
"""
|
|
675
|
+
# fmt: on
|
|
660
676
|
dims = data_array.dims
|
|
661
677
|
coords = []
|
|
662
678
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Experimental parts of Coordax."""
|
|
15
|
+
|
|
16
|
+
# Note: import <name> as <name> is required for names to be exported.
|
|
17
|
+
# See PEP 484 & https://github.com/jax-ml/jax/issues/7570
|
|
18
|
+
# pylint: disable=g-multiple-import,useless-import-alias,g-importing-member,unused-import
|
|
19
|
+
from coordax.ndarrays import (
|
|
20
|
+
NDArray as NDArray,
|
|
21
|
+
register_ndarray as register_ndarray,
|
|
22
|
+
)
|
|
@@ -76,7 +76,7 @@ def _axes_attrs(field: Field) -> str:
|
|
|
76
76
|
|
|
77
77
|
@utils.export
|
|
78
78
|
def new_axis_name(field: Field, excluded_names: set[str] | None = None) -> str:
|
|
79
|
-
"""Returns axis name that is not present in
|
|
79
|
+
"""Returns axis name that is not present in ``field`` or ``excluded_names``.
|
|
80
80
|
|
|
81
81
|
Args:
|
|
82
82
|
field: The field to generate a new axis name for.
|
|
@@ -106,7 +106,7 @@ def new_axis_name(field: Field, excluded_names: set[str] | None = None) -> str:
|
|
|
106
106
|
|
|
107
107
|
@utils.export
|
|
108
108
|
def tmp_axis_name(field: Field, excluded_names: set[str] | None = None) -> str:
|
|
109
|
-
"""Deprecated alias for
|
|
109
|
+
"""Deprecated alias for ``new_axis_name``."""
|
|
110
110
|
warnings.warn(
|
|
111
111
|
'cx.tmp_axis_name() is deprecated, use cx.new_axis_name() instead',
|
|
112
112
|
DeprecationWarning,
|
|
@@ -125,7 +125,7 @@ def cmap(
|
|
|
125
125
|
vmap: Callable = jax.vmap, # pylint: disable=g-bare-generic
|
|
126
126
|
) -> Callable[..., Any]:
|
|
127
127
|
# fmt: off
|
|
128
|
-
"""Vectorizes
|
|
128
|
+
"""Vectorizes ``fun`` over coordinate dimensions of ``Field`` inputs.
|
|
129
129
|
|
|
130
130
|
``cmap`` is a "coordinate vectorizing map". It wraps an ordinary
|
|
131
131
|
positional-axis-based function so that it accepts ``Field`` objects as input
|
|
@@ -244,7 +244,7 @@ def cpmap(
|
|
|
244
244
|
coordax compatible with custom objects (e.g. neural net modules).
|
|
245
245
|
|
|
246
246
|
Returns:
|
|
247
|
-
A function that applies
|
|
247
|
+
A function that applies ``fun`` to positional axes of the inputs while
|
|
248
248
|
vectorizing over all coordinate dimensions. The coordinate order of the
|
|
249
249
|
inputs is preserved in the outputs.
|
|
250
250
|
|
|
@@ -264,8 +264,8 @@ def cpmap(
|
|
|
264
264
|
Traceback (most recent call last):
|
|
265
265
|
...
|
|
266
266
|
ValueError: 'same_as_input' for out_axes requires all NamedArray inputs with
|
|
267
|
-
named axes to have the same
|
|
268
|
-
|
|
267
|
+
named axes to have the same named_axes. Found multiple distinct
|
|
268
|
+
named_axes on inputs:
|
|
269
269
|
[{'x': 0, 'y': 1}, {'y': 0, 'x': 1}]
|
|
270
270
|
|
|
271
271
|
See also:
|
|
@@ -314,8 +314,8 @@ def _cmap_with_doc(
|
|
|
314
314
|
)
|
|
315
315
|
|
|
316
316
|
docstr = (
|
|
317
|
-
f'Dimension-vectorized version of
|
|
318
|
-
f' as
|
|
317
|
+
f'Dimension-vectorized version of ``{fun_name}``. Takes similar arguments'
|
|
318
|
+
f' as ``{fun_name}`` but accepts and returns Fields in place of arrays.'
|
|
319
319
|
)
|
|
320
320
|
if fun_doc:
|
|
321
321
|
docstr += f'\n\nOriginal documentation:\n\n{fun_doc}'
|
|
@@ -405,8 +405,8 @@ def _wrap_array_method(name):
|
|
|
405
405
|
wrapped_func.__module__ = __name__
|
|
406
406
|
wrapped_func.__doc__ = (
|
|
407
407
|
'Name-vectorized version of array method'
|
|
408
|
-
f'
|
|
409
|
-
f'
|
|
408
|
+
f' ``{name} <numpy.ndarray.{name}>``. Takes similar arguments as'
|
|
409
|
+
f' ``{name} <numpy.ndarray.{name}>`` but accepts and returns Fields'
|
|
410
410
|
' in place of regular arrays.'
|
|
411
411
|
)
|
|
412
412
|
return wrapped_func
|
|
@@ -421,6 +421,7 @@ def _in_treescope_abbreviation_mode() -> bool:
|
|
|
421
421
|
@jax.tree_util.register_pytree_node_class
|
|
422
422
|
class Field:
|
|
423
423
|
# pylint: disable=line-too-long
|
|
424
|
+
# fmt: off
|
|
424
425
|
"""An array with optional named dimensions and associated coordinates.
|
|
425
426
|
|
|
426
427
|
Examples:
|
|
@@ -457,6 +458,7 @@ class Field:
|
|
|
457
458
|
>>> field.coordinate
|
|
458
459
|
CartesianProduct(coordinates=(coordax.SizedAxis('x', size=2), coordax.DummyAxis('y', size=3), coordax.DummyAxis(None, size=4)))
|
|
459
460
|
"""
|
|
461
|
+
# fmt on
|
|
460
462
|
|
|
461
463
|
_named_array: named_axes_lib.NamedArray
|
|
462
464
|
_axes: dict[str, Coordinate]
|
|
@@ -472,11 +474,11 @@ class Field:
|
|
|
472
474
|
Args:
|
|
473
475
|
data: the underlying data array.
|
|
474
476
|
dims: optional tuple of dimension names, with the same length as
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
are positional.
|
|
477
|
+
``data.ndim``. Strings indicate named axes, and may not be repeated.
|
|
478
|
+
``None`` indicates positional axes. If ``dims`` is not provided, all
|
|
479
|
+
axes are positional.
|
|
478
480
|
axes: optional mapping from dimension names to associated
|
|
479
|
-
|
|
481
|
+
``coordax.Coordinate`` objects.
|
|
480
482
|
|
|
481
483
|
Examples:
|
|
482
484
|
>>> import coordax as cx
|
|
@@ -645,8 +647,9 @@ class Field:
|
|
|
645
647
|
def unwrap(self, *names: str | Coordinate) -> Array:
|
|
646
648
|
"""Extracts underlying data from a field without named dimensions.
|
|
647
649
|
|
|
648
|
-
This is effectively syntactic sugar for
|
|
649
|
-
|
|
650
|
+
This is effectively syntactic sugar for ``assert field.named_dims ==
|
|
651
|
+
names``,
|
|
652
|
+
followed by returning ``field.data``.
|
|
650
653
|
|
|
651
654
|
Args:
|
|
652
655
|
*names: Names of dimensions to check against.
|
|
@@ -655,7 +658,7 @@ class Field:
|
|
|
655
658
|
The underlying data array.
|
|
656
659
|
|
|
657
660
|
Raises:
|
|
658
|
-
ValueError: If the field has named dimensions that do not match
|
|
661
|
+
ValueError: If the field has named dimensions that do not match ``names``.
|
|
659
662
|
|
|
660
663
|
Examples:
|
|
661
664
|
>>> import coordax as cx
|
|
@@ -678,7 +681,8 @@ class Field:
|
|
|
678
681
|
return self.data
|
|
679
682
|
|
|
680
683
|
def _validate_matching_coords(
|
|
681
|
-
self,
|
|
684
|
+
self,
|
|
685
|
+
dims_or_coords: Sequence[str | Coordinate | types.EllipsisType],
|
|
682
686
|
):
|
|
683
687
|
"""Validate that given coordinates are all found on this field."""
|
|
684
688
|
axes = []
|
|
@@ -716,6 +720,8 @@ class Field:
|
|
|
716
720
|
|
|
717
721
|
Untag by name:
|
|
718
722
|
|
|
723
|
+
>>> field.untag('x')
|
|
724
|
+
<Field dims=(None, 'y') shape=(2, 3) axes={} >
|
|
719
725
|
>>> field.untag('y')
|
|
720
726
|
<Field dims=('x', None) shape=(2, 3) axes={'x': SizedAxis} >
|
|
721
727
|
|
|
@@ -741,8 +747,8 @@ class Field:
|
|
|
741
747
|
Args:
|
|
742
748
|
*names: Names or coordinates to assign to the positional axes. The total
|
|
743
749
|
number of dimensions corresponding to these objects must match the
|
|
744
|
-
number of positional axes in the field unless
|
|
745
|
-
positions of untagged axes.
|
|
750
|
+
number of positional axes in the field unless ``...`` is used to
|
|
751
|
+
indicate positions of untagged axes.
|
|
746
752
|
|
|
747
753
|
Returns:
|
|
748
754
|
A new Field with the specified dimensions tagged.
|
|
@@ -754,18 +760,18 @@ class Field:
|
|
|
754
760
|
>>> field.tag('x', 'y')
|
|
755
761
|
<Field dims=('x', 'y') shape=(2, 3) axes={} >
|
|
756
762
|
|
|
757
|
-
Tagging with
|
|
763
|
+
Tagging with ``Coordinate`` objects adds them to the field:
|
|
758
764
|
|
|
759
765
|
>>> x = cx.SizedAxis('x', 2)
|
|
760
766
|
>>> field.tag(x, 'y')
|
|
761
767
|
<Field dims=('x', 'y') shape=(2, 3) axes={'x': SizedAxis} >
|
|
762
768
|
|
|
763
|
-
|
|
769
|
+
``None`` leaves a dimension untagged:
|
|
764
770
|
|
|
765
771
|
>>> field.tag('x', None)
|
|
766
772
|
<Field dims=('x', None) shape=(2, 3) axes={} >
|
|
767
773
|
|
|
768
|
-
Ellipsis
|
|
774
|
+
Ellipsis ``...`` can be indicate dimensions that should not be named:
|
|
769
775
|
|
|
770
776
|
>>> field.tag(..., 'y')
|
|
771
777
|
<Field dims=(None, 'y') shape=(2, 3) axes={} >
|
|
@@ -775,7 +781,7 @@ class Field:
|
|
|
775
781
|
>>> field.tag('x') # doctest: +NORMALIZE_WHITESPACE
|
|
776
782
|
Traceback (most recent call last):
|
|
777
783
|
...
|
|
778
|
-
ValueError: there must be exactly as many dimensions given to
|
|
784
|
+
ValueError: there must be exactly as many dimensions given to ``tag`` as
|
|
779
785
|
there are positional axes in the array, but got ('x',) for 2 positional
|
|
780
786
|
axes.
|
|
781
787
|
|
|
@@ -790,7 +796,8 @@ class Field:
|
|
|
790
796
|
CartesianProduct(axes={'x': SizedAxis, 'y': SizedAxis})
|
|
791
797
|
>>> field = cx.Field(jnp.zeros((2, 3)))
|
|
792
798
|
>>> field.tag(xy)
|
|
793
|
-
<Field dims=('x', 'y') shape=(2, 3) axes={'x': SizedAxis, 'y': SizedAxis}
|
|
799
|
+
<Field dims=('x', 'y') shape=(2, 3) axes={'x': SizedAxis, 'y': SizedAxis}
|
|
800
|
+
>
|
|
794
801
|
|
|
795
802
|
See also:
|
|
796
803
|
:meth:`coordax.Field.untag`
|
|
@@ -812,12 +819,14 @@ class Field:
|
|
|
812
819
|
|
|
813
820
|
# Note: Can't call this "transpose" like Xarray, to avoid conflicting with the
|
|
814
821
|
# positional only ndarray method.
|
|
815
|
-
def order_as(
|
|
822
|
+
def order_as(
|
|
823
|
+
self, *axis_order: str | Coordinate | types.EllipsisType
|
|
824
|
+
) -> Field:
|
|
816
825
|
"""Returns a field with the axes in the given order.
|
|
817
826
|
|
|
818
827
|
Args:
|
|
819
828
|
*axis_order: The desired order of axes, specified by name or coordinate.
|
|
820
|
-
|
|
829
|
+
``...`` may be used once, to indicate all other dimensions in order of
|
|
821
830
|
appearance on this array.
|
|
822
831
|
|
|
823
832
|
Returns:
|
|
@@ -839,13 +848,13 @@ class Field:
|
|
|
839
848
|
return result
|
|
840
849
|
|
|
841
850
|
def broadcast_like(self, other: Self | Coordinate) -> Self:
|
|
842
|
-
"""Returns a field broadcasted like
|
|
851
|
+
"""Returns a field broadcasted like ``other``.
|
|
843
852
|
|
|
844
853
|
Args:
|
|
845
854
|
other: The field or coordinate to broadcast to.
|
|
846
855
|
|
|
847
856
|
Returns:
|
|
848
|
-
A new Field broadcasted to match
|
|
857
|
+
A new Field broadcasted to match ``other``.
|
|
849
858
|
|
|
850
859
|
Examples:
|
|
851
860
|
>>> import coordax as cx
|
|
@@ -1010,7 +1019,7 @@ class Field:
|
|
|
1010
1019
|
def field(array: ArrayLike, *names: str | Coordinate | None) -> Field:
|
|
1011
1020
|
"""Wraps a positional array as a ``Field``.
|
|
1012
1021
|
|
|
1013
|
-
|
|
1022
|
+
``cx.field(array, *names)`` is a shortcut for ``cx.Field(array).tag(*names)``.
|
|
1014
1023
|
|
|
1015
1024
|
Args:
|
|
1016
1025
|
array: the array to wrap.
|
|
@@ -1037,7 +1046,7 @@ def field(array: ArrayLike, *names: str | Coordinate | None) -> Field:
|
|
|
1037
1046
|
|
|
1038
1047
|
@utils.export
|
|
1039
1048
|
def wrap(array: ArrayLike, *names: str | Coordinate | None) -> Field:
|
|
1040
|
-
"""Deprecated alias for
|
|
1049
|
+
"""Deprecated alias for ``cx.field``."""
|
|
1041
1050
|
warnings.warn(
|
|
1042
1051
|
'cx.wrap() is deprecated, use cx.field() instead',
|
|
1043
1052
|
DeprecationWarning,
|
|
@@ -1048,7 +1057,7 @@ def wrap(array: ArrayLike, *names: str | Coordinate | None) -> Field:
|
|
|
1048
1057
|
|
|
1049
1058
|
@utils.export
|
|
1050
1059
|
def wrap_like(array: ArrayLike, other: Field) -> Field:
|
|
1051
|
-
"""Wraps
|
|
1060
|
+
"""Wraps ``array`` with the same coordinates as ``other``."""
|
|
1052
1061
|
warnings.warn(
|
|
1053
1062
|
'cx.wrap_like() is deprecated, use cx.field(array, other.coordinate) '
|
|
1054
1063
|
'instead of cx.wrap_like(array, other)',
|
|
@@ -1068,6 +1077,7 @@ def from_xarray(
|
|
|
1068
1077
|
coord_types: Sequence[type[Coordinate]] = (LabeledAxis, DummyAxis),
|
|
1069
1078
|
) -> Field:
|
|
1070
1079
|
# pylint: disable=g-import-not-at-top,line-too-long
|
|
1080
|
+
# fmt: off
|
|
1071
1081
|
"""Create a coordax.Field from an xarray.DataArray.
|
|
1072
1082
|
|
|
1073
1083
|
Args:
|
|
@@ -1092,6 +1102,7 @@ def from_xarray(
|
|
|
1092
1102
|
See also:
|
|
1093
1103
|
:func:`coordax.coords.from_xarray`
|
|
1094
1104
|
"""
|
|
1105
|
+
# fmt: on
|
|
1095
1106
|
data = data_array.data
|
|
1096
1107
|
coord = coordinate_systems.from_xarray(data_array, coord_types)
|
|
1097
1108
|
return field(data, coord)
|
|
@@ -1099,19 +1110,19 @@ def from_xarray(
|
|
|
1099
1110
|
|
|
1100
1111
|
@utils.export
|
|
1101
1112
|
def is_field(value) -> TypeGuard[Field]:
|
|
1102
|
-
"""Returns True if
|
|
1113
|
+
"""Returns True if ``value`` is of type ``Field``."""
|
|
1103
1114
|
return isinstance(value, Field)
|
|
1104
1115
|
|
|
1105
1116
|
|
|
1106
1117
|
@utils.export
|
|
1107
1118
|
def shape_struct_field(*axes: Coordinate) -> Field:
|
|
1108
|
-
"""Returns a Field with
|
|
1119
|
+
"""Returns a Field with ``axes`` and a ShapeDtypeStruct in place of data.
|
|
1109
1120
|
|
|
1110
1121
|
Args:
|
|
1111
1122
|
*axes: The coordinates to use for the field.
|
|
1112
1123
|
|
|
1113
1124
|
Returns:
|
|
1114
|
-
A Field with
|
|
1125
|
+
A Field with ``ShapeDtypeStruct`` data and the given axes.
|
|
1115
1126
|
|
|
1116
1127
|
Examples:
|
|
1117
1128
|
>>> import coordax as cx
|
|
@@ -1142,12 +1153,12 @@ def get_coordinate(
|
|
|
1142
1153
|
field: coordax.Field from which the coordinate will be extracted.
|
|
1143
1154
|
missing_axes: controls how axes without coorinates are handled. Options are:
|
|
1144
1155
|
|
|
1145
|
-
* ``'dummy'``: uses DummyAxis for dimensions without a coordinate.
|
|
1156
|
+
* ``'dummy'``: uses ``DummyAxis`` for dimensions without a coordinate.
|
|
1146
1157
|
* ``'skip'``: ignores dimensions without a coordinate.
|
|
1147
1158
|
* ``'error'``: raises if dimensions without a coordinate are present.
|
|
1148
1159
|
|
|
1149
1160
|
Returns:
|
|
1150
|
-
Coordinate associated with the
|
|
1161
|
+
Coordinate associated with the ``field``.
|
|
1151
1162
|
|
|
1152
1163
|
Examples:
|
|
1153
1164
|
>>> import coordax as cx
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
This module is intended to be nearly a drop-in replacement for Penzai's
|
|
17
17
|
NamedArray, but with an alternative, simpler implementation. Dimensions are
|
|
18
|
-
specified by a tuple, where each element is either a string or
|
|
19
|
-
used to indicate strictly positional dimensions.
|
|
18
|
+
specified by a tuple, where each element is either a string or ``None``.
|
|
19
|
+
``None`` is used to indicate strictly positional dimensions.
|
|
20
20
|
|
|
21
21
|
Some code and documentation is adapted from penzai.core.named_axes.
|
|
22
22
|
"""
|
|
@@ -49,7 +49,7 @@ def attrs_summary_type(
|
|
|
49
49
|
named_array: NamedArray,
|
|
50
50
|
inspect_data: bool,
|
|
51
51
|
) -> tuple[str, str, str]:
|
|
52
|
-
"""Returns a summary of a
|
|
52
|
+
"""Returns a summary of a ``named_array`` and its data type."""
|
|
53
53
|
if isinstance(named_array.data, jax.Array) and not isinstance(
|
|
54
54
|
named_array.data, jax.core.Tracer
|
|
55
55
|
):
|
|
@@ -111,8 +111,8 @@ class NamedArrayAdapter(ndarray_adapters.NDArrayAdapter['NamedArray']):
|
|
|
111
111
|
bad_names = set(mask.dims) - set(array.dims)
|
|
112
112
|
if bad_names:
|
|
113
113
|
raise ValueError(
|
|
114
|
-
'Valid mask must be broadcastable to the shape of
|
|
115
|
-
f' had extra axis names {bad_names}'
|
|
114
|
+
'Valid mask must be broadcastable to the shape of ``array``, but '
|
|
115
|
+
f'it had extra axis names {bad_names}'
|
|
116
116
|
)
|
|
117
117
|
mask = mask.order_as(*(d for d in array.dims if d in mask.dims))
|
|
118
118
|
mask_data = mask.data
|
|
@@ -240,8 +240,8 @@ def _normalize_out_axes(
|
|
|
240
240
|
named_axes_list = [arr.named_axes for arr in named_arrays_with_axes]
|
|
241
241
|
raise ValueError(
|
|
242
242
|
"'same_as_input' for out_axes requires all NamedArray inputs with"
|
|
243
|
-
' named axes to have the same
|
|
244
|
-
f' distinct
|
|
243
|
+
' named axes to have the same named_axes. Found multiple'
|
|
244
|
+
f' distinct named_axes on inputs:\n{named_axes_list}'
|
|
245
245
|
)
|
|
246
246
|
[unique_named_axes] = unique_named_axes
|
|
247
247
|
return dict(unique_named_axes)
|
|
@@ -304,22 +304,22 @@ def nmap(
|
|
|
304
304
|
"""Automatically vectorizes ``fun`` over named dimensions.
|
|
305
305
|
|
|
306
306
|
``nmap`` is a "named dimension vectorizing map". It wraps an ordinary
|
|
307
|
-
positional-axis-based function so that it accepts
|
|
308
|
-
produces
|
|
307
|
+
positional-axis-based function so that it accepts ``NamedArray``s as input and
|
|
308
|
+
produces ``NamedArray``s as output, and vectorizes over all of named dimensions,
|
|
309
309
|
calling the original function with positionally-indexed slices corresponding
|
|
310
|
-
to each argument's
|
|
310
|
+
to each argument's ``positional_shape``.
|
|
311
311
|
|
|
312
|
-
Unlike
|
|
312
|
+
Unlike ``jax.vmap``, the axes to vectorize over are inferred
|
|
313
313
|
automatically from the named dimensions in the NamedArray inputs, rather
|
|
314
314
|
than being specified as part of the mapping transformation. Specifically, each
|
|
315
315
|
dimension name that appears in any of the arguments is vectorized over jointly
|
|
316
316
|
across all arguments that include that dimension, and is then included as a
|
|
317
317
|
named dimension in the output. To make an axis visible to ``fun``, you can
|
|
318
|
-
call
|
|
318
|
+
call ``untag`` on the argument and pass the axis name(s) of interest; ``fun``
|
|
319
319
|
will then see those axes as positional axes instead of mapping over them.
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
operations to axes of a NamedArray.
|
|
321
|
+
``untag`` and ``nmap`` are together the primary ways to apply individual
|
|
322
|
+
operations to axes of a NamedArray. ``tag`` can then be used on the result to
|
|
323
323
|
re-bind names to positional axes.
|
|
324
324
|
|
|
325
325
|
Within ``fun``, any mapped-over axes will be accessible using standard JAX
|
|
@@ -347,8 +347,8 @@ def nmap(
|
|
|
347
347
|
|
|
348
348
|
Returns:
|
|
349
349
|
An automatically-vectorized version of ``fun``, which can optionally be
|
|
350
|
-
called with
|
|
351
|
-
return
|
|
350
|
+
called with ``NamedArray``s instead of ordinary arrays, and which will always
|
|
351
|
+
return ``NamedArray``s for each of its output leaves. Any argument (or PyTree
|
|
352
352
|
leaf of an argument) that is a NamedArray will have its named dimensions
|
|
353
353
|
vectorized over; ``fun`` will then be called with batch tracers
|
|
354
354
|
corresponding to slices of the input array that are shaped like
|
|
@@ -452,8 +452,8 @@ def _nmap_with_doc(
|
|
|
452
452
|
return jax.tree.map(wrap_output, result, is_leaf=is_array)
|
|
453
453
|
|
|
454
454
|
docstr = (
|
|
455
|
-
f'Dimension-vectorized version of
|
|
456
|
-
f' as
|
|
455
|
+
f'Dimension-vectorized version of ``{fun_name}``. Takes similar arguments'
|
|
456
|
+
f' as ``{fun_name}`` but accepts and returns NamedArray objects in place '
|
|
457
457
|
'of arrays.'
|
|
458
458
|
)
|
|
459
459
|
if fun_doc:
|
|
@@ -536,8 +536,8 @@ def _wrap_array_method(
|
|
|
536
536
|
wrapped_func.__module__ = __name__
|
|
537
537
|
wrapped_func.__doc__ = (
|
|
538
538
|
'Name-vectorized version of array method'
|
|
539
|
-
f'
|
|
540
|
-
f'
|
|
539
|
+
f' ``{name} <numpy.ndarray.{name}>``. Takes similar arguments as'
|
|
540
|
+
f' ``{name} <numpy.ndarray.{name}>`` but accepts and returns NamedArray'
|
|
541
541
|
' objects in place of regular arrays.'
|
|
542
542
|
)
|
|
543
543
|
return wrapped_func
|
|
@@ -560,7 +560,7 @@ class _ShapedLeaf:
|
|
|
560
560
|
|
|
561
561
|
|
|
562
562
|
def _tmp_axis_name(x: NamedArray, excluded_names: set[str]) -> str:
|
|
563
|
-
"""Returns axis name that is not present in
|
|
563
|
+
"""Returns axis name that is not present in ``x`` or ``excluded_names``."""
|
|
564
564
|
for i in range(x.ndim):
|
|
565
565
|
name = f'tmp_axis_{i}'
|
|
566
566
|
if name not in excluded_names and name not in x.named_dims:
|
|
@@ -582,9 +582,9 @@ class NamedArray:
|
|
|
582
582
|
|
|
583
583
|
Attributes:
|
|
584
584
|
data: the underlying data array.
|
|
585
|
-
dims: tuple of dimension names, with the same length as
|
|
586
|
-
indicate named axes, and may not be repeated.
|
|
587
|
-
axes.
|
|
585
|
+
dims: tuple of dimension names, with the same length as ``data.ndim``.
|
|
586
|
+
Strings indicate named axes, and may not be repeated. ``None`` indicates
|
|
587
|
+
positional axes.
|
|
588
588
|
"""
|
|
589
589
|
|
|
590
590
|
_data: Array
|
|
@@ -600,9 +600,9 @@ class NamedArray:
|
|
|
600
600
|
Arguments:
|
|
601
601
|
data: the underlying data array.
|
|
602
602
|
dims: optional tuple of dimension names, with the same length as
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
are positional.
|
|
603
|
+
``data.ndim``. Strings indicate named axes, and may not be repeated.
|
|
604
|
+
``None`` indicates positional axes. If ``dims`` is not provided, all
|
|
605
|
+
axes are positional.
|
|
606
606
|
"""
|
|
607
607
|
data = ndarrays.to_array(data)
|
|
608
608
|
if dims is None:
|
|
@@ -804,8 +804,8 @@ class NamedArray:
|
|
|
804
804
|
def untag(self, *dims: str) -> Self:
|
|
805
805
|
"""Removes the requested dimension names.
|
|
806
806
|
|
|
807
|
-
|
|
808
|
-
positional axes. It produces a new
|
|
807
|
+
``untag`` can only be called on a ``NamedArray`` that does not have any
|
|
808
|
+
positional axes. It produces a new ``NamedArray`` where the axes with the
|
|
809
809
|
requested dimension names are now treated as positional instead.
|
|
810
810
|
|
|
811
811
|
Args:
|
|
@@ -846,12 +846,12 @@ class NamedArray:
|
|
|
846
846
|
def order_as(self, *dims: str | types.EllipsisType) -> Self:
|
|
847
847
|
"""Reorder the dimensions of an array.
|
|
848
848
|
|
|
849
|
-
All dimensions must be named. Use
|
|
849
|
+
All dimensions must be named. Use ``tag`` first to name any positional axes.
|
|
850
850
|
|
|
851
851
|
Args:
|
|
852
852
|
*dims: dimension names that appear on this array, in the desired order on
|
|
853
|
-
the result.
|
|
854
|
-
order of appearance on this array.
|
|
853
|
+
the result. ``...`` may be used once, to indicate all other dimensions
|
|
854
|
+
in order of appearance on this array.
|
|
855
855
|
|
|
856
856
|
Returns:
|
|
857
857
|
Array with transposed data and reordered dimensions, as indicated.
|
|
@@ -110,7 +110,7 @@ class NamedAxesTest(parameterized.TestCase):
|
|
|
110
110
|
with self.assertRaisesWithLiteralMatch(
|
|
111
111
|
TypeError,
|
|
112
112
|
'data must be a np.ndarray, jax.Array or a duck-typed array registered'
|
|
113
|
-
' with coordax.register_ndarray(), got dict: {}',
|
|
113
|
+
' with coordax.experimental.register_ndarray(), got dict: {}',
|
|
114
114
|
):
|
|
115
115
|
named_axes.NamedArray({})
|
|
116
116
|
with self.assertRaisesRegex(
|
|
@@ -784,9 +784,9 @@ class NamedAxesTest(parameterized.TestCase):
|
|
|
784
784
|
ValueError,
|
|
785
785
|
re.escape(
|
|
786
786
|
"'same_as_input' for out_axes requires all NamedArray inputs with"
|
|
787
|
-
' named axes to have the same
|
|
788
|
-
" distinct
|
|
789
|
-
" 'y': 1, 'x': 2}]"
|
|
787
|
+
' named axes to have the same named_axes. Found multiple'
|
|
788
|
+
" distinct named_axes on inputs:\n"
|
|
789
|
+
"[{'x': 0, 'y': 1, 'z': 2}, {'z': 0, 'y': 1, 'x': 2}]"
|
|
790
790
|
),
|
|
791
791
|
):
|
|
792
792
|
named_axes.nmap(lambda x, y: x, out_axes='same_as_input')(array1, array2)
|
|
@@ -796,8 +796,8 @@ class NamedAxesTest(parameterized.TestCase):
|
|
|
796
796
|
with self.assertRaisesWithLiteralMatch(
|
|
797
797
|
ValueError,
|
|
798
798
|
"'same_as_input' for out_axes requires all NamedArray inputs with"
|
|
799
|
-
' named axes to have the same
|
|
800
|
-
" distinct
|
|
799
|
+
' named axes to have the same named_axes. Found multiple'
|
|
800
|
+
" distinct named_axes on inputs:\n[{'x': 0}, {'x': 0, 'y': 1}]"
|
|
801
801
|
):
|
|
802
802
|
named_axes.nmap(lambda x, y: x, out_axes='same_as_input')(array1, array2)
|
|
803
803
|
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import abc
|
|
19
|
+
import functools
|
|
19
20
|
from typing import Any, Callable, Self, TypeVar
|
|
20
21
|
|
|
22
|
+
from coordax import utils
|
|
21
23
|
import jax
|
|
22
24
|
import numpy as np
|
|
23
25
|
|
|
@@ -28,10 +30,15 @@ except ImportError:
|
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
# TODO(shoyer): should this be a protocol?
|
|
33
|
+
@functools.partial(utils.export, module='coordax.experimental')
|
|
31
34
|
class NDArray(abc.ABC):
|
|
32
35
|
"""Abstract base class for non-JAX arrays that can be used with Coordax.
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
THIS FEATURE IS EXPERIMENTAL, AND THE DETAILS ARE SUBJECT TO CHANGE. We
|
|
38
|
+
currently use it only to support jax-datetime, but expect it might be useful
|
|
39
|
+
in the future for other array types, e.g., with arbitrary units.
|
|
40
|
+
|
|
41
|
+
To register a new NDArray, use :func:`coordax.experimental.register_ndarray`.
|
|
35
42
|
|
|
36
43
|
NDArray instances are also expected to be registered as JAX pytree nodes:
|
|
37
44
|
https://docs.jax.dev/en/latest/pytrees.html#extending-pytrees
|
|
@@ -45,12 +52,12 @@ class NDArray(abc.ABC):
|
|
|
45
52
|
@property
|
|
46
53
|
@abc.abstractmethod
|
|
47
54
|
def size(self) -> int:
|
|
48
|
-
"""Size of this array, typically
|
|
55
|
+
"""Size of this array, typically ``math.prod(self.shape)``."""
|
|
49
56
|
|
|
50
57
|
@property
|
|
51
58
|
@abc.abstractmethod
|
|
52
59
|
def ndim(self) -> int:
|
|
53
|
-
"""Number of dimensions in this array, typcially
|
|
60
|
+
"""Number of dimensions in this array, typcially ``len(self.shape)``."""
|
|
54
61
|
|
|
55
62
|
@abc.abstractmethod
|
|
56
63
|
def transpose(self, axes: tuple[int, ...]) -> Self:
|
|
@@ -58,7 +65,7 @@ class NDArray(abc.ABC):
|
|
|
58
65
|
|
|
59
66
|
# Note: __getitem__ is not yet used by Coordax, but we will likely want it in
|
|
60
67
|
# the near the future.
|
|
61
|
-
# TODO(shoyer): Figure out the precise type for
|
|
68
|
+
# TODO(shoyer): Figure out the precise type for ``value``.
|
|
62
69
|
@abc.abstractmethod
|
|
63
70
|
def __getitem__(self, value) -> Self:
|
|
64
71
|
"""Index this array, returning a new array."""
|
|
@@ -72,7 +79,7 @@ Array = np.ndarray | jax.Array | NDArray
|
|
|
72
79
|
|
|
73
80
|
|
|
74
81
|
def to_array(data: ArrayLike) -> Array:
|
|
75
|
-
"""Returns an NDArray compatible with JAX and Coordax.
|
|
82
|
+
"""Returns an unlabeled NDArray compatible with JAX and Coordax.
|
|
76
83
|
|
|
77
84
|
Args:
|
|
78
85
|
data: a scalar, NumPy array, JAX array, or registered NDArray.
|
|
@@ -93,7 +100,8 @@ def to_array(data: ArrayLike) -> Array:
|
|
|
93
100
|
if not isinstance(data, (np.ndarray, jax.Array, NDArray)):
|
|
94
101
|
raise TypeError(
|
|
95
102
|
'data must be a np.ndarray, jax.Array or a duck-typed array registered '
|
|
96
|
-
|
|
103
|
+
'with coordax.experimental.register_ndarray(), got '
|
|
104
|
+
f'{type(data).__name__}: {data}'
|
|
97
105
|
)
|
|
98
106
|
|
|
99
107
|
return data
|
|
@@ -119,13 +127,19 @@ _FROM_NUMPY_FUNCS: list[
|
|
|
119
127
|
] = []
|
|
120
128
|
|
|
121
129
|
|
|
130
|
+
@functools.partial(utils.export, module='coordax.experimental')
|
|
122
131
|
def register_ndarray(
|
|
123
132
|
array_type: type[T],
|
|
124
133
|
is_matching_numpy_array: Callable[[np.ndarray], bool],
|
|
125
134
|
to_numpy: Callable[[T], np.ndarray],
|
|
126
135
|
from_numpy: Callable[[np.ndarray], T],
|
|
127
136
|
) -> type[T]:
|
|
128
|
-
|
|
137
|
+
# pylint: disable=g-doc-args
|
|
138
|
+
"""Registers a new duck-typed array type corresponding to a numpy dtype.
|
|
139
|
+
|
|
140
|
+
See also:
|
|
141
|
+
:class:`coordax.experimental.NDArray`
|
|
142
|
+
"""
|
|
129
143
|
NDArray.register(array_type)
|
|
130
144
|
_TO_NUMPY_FUNCS.append((array_type, to_numpy))
|
|
131
145
|
_FROM_NUMPY_FUNCS.append((is_matching_numpy_array, from_numpy))
|
|
@@ -20,7 +20,7 @@ def assert_field_properties(
|
|
|
20
20
|
rtol: float | None = 1e-5,
|
|
21
21
|
atol: float | None = 1e-5,
|
|
22
22
|
):
|
|
23
|
-
"""Asserts that a Field has expected properties."""
|
|
23
|
+
"""Asserts that a ``Field`` has expected properties."""
|
|
24
24
|
if data is not None:
|
|
25
25
|
if atol is None and rtol is None:
|
|
26
26
|
chex.assert_trees_all_equal(actual.data, data)
|
|
@@ -48,7 +48,7 @@ def assert_fields_allclose(
|
|
|
48
48
|
rtol: float = 1e-5,
|
|
49
49
|
atol: float = 1e-5,
|
|
50
50
|
):
|
|
51
|
-
"""Asserts that two
|
|
51
|
+
"""Asserts that two ``Field``s are close and have matching coordinates."""
|
|
52
52
|
assert_field_properties(
|
|
53
53
|
actual=actual,
|
|
54
54
|
data=desired.data,
|
|
@@ -63,7 +63,7 @@ def assert_fields_allclose(
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def assert_fields_equal(actual: coordax.Field, desired: coordax.Field):
|
|
66
|
-
"""Asserts that two
|
|
66
|
+
"""Asserts that two ``Field``s are equal and have matching coordinates."""
|
|
67
67
|
assert_field_properties(
|
|
68
68
|
actual=actual,
|
|
69
69
|
data=desired.data,
|
|
@@ -7,7 +7,7 @@ packages = ["coordax"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "coordax"
|
|
10
|
-
version = "0.2.
|
|
10
|
+
version = "0.2.2" # keep sync with __init__.py
|
|
11
11
|
description = "Coordinate axes for scientific computing in JAX"
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "Google LLC", email = "noreply@google.com"},
|
|
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
|