Repository: sdarray/sdarray

Issue:
Add definitions of coordinates

Discussion:

Patch:
diff --git a/poetry.lock b/poetry.lock
index 13ffe1c..4f754fc 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -168,7 +168,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "importlib-metadata"
-version = "4.8.1"
+version = "4.8.2"
 description = "Read metadata from Python packages"
 category = "main"
 optional = false
@@ -181,7 +181,7 @@ zipp = ">=0.5"
 [package.extras]
 docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
 perf = ["ipython"]
-testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
 
 [[package]]
 name = "iniconfig"
@@ -756,7 +756,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
 [metadata]
 lock-version = "1.1"
 python-versions = ">=3.7, <3.10"
-content-hash = "71e91999ab55f72c82b5679796ec294e4576a38cb68b457696ae84614056dcf6"
+content-hash = "b7a8d7b9d1828c70f90baf17740a773562bc3dfdcf24e61692ae95d5e83948ac"
 
 [metadata.files]
 alabaster = [
@@ -824,8 +824,8 @@ imagesize = [
     {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
-    {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
+    {file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"},
+    {file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"},
 ]
 iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
diff --git a/pyproject.toml b/pyproject.toml
index dc08c68..28dbb54 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,6 +10,7 @@ documentation = "https://sdarray.github.io/sdarray"
 
 [tool.poetry.dependencies]
 python = ">=3.7, <3.10"
+numpy = "^1.19"
 typing-extensions = "^3.10"
 xarray-dataclasses = "^0.6"
 
diff --git a/sdarray/__init__.py b/sdarray/__init__.py
index c933749..d99aa8a 100644
--- a/sdarray/__init__.py
+++ b/sdarray/__init__.py
@@ -3,4 +3,5 @@ __author__ = "Akio Taniguchi"
 
 
 # submodules
+from . import coords
 from . import dims
diff --git a/sdarray/coords.py b/sdarray/coords.py
new file mode 100644
index 0000000..4fbfe26
--- /dev/null
+++ b/sdarray/coords.py
@@ -0,0 +1,211 @@
+__all__ = [
+    "Scan",
+    "Mode",
+    "Exposure",
+    "Interval",
+    "Longitude",
+    "Latitude",
+    "Beam",
+    "SpW",
+    "Pol",
+    "CenterFreq",
+    "RefFreq",
+    "Resolution",
+    "Width",
+    "LonOffset",
+    "LatOffset",
+    "Mask",
+    "Sigma",
+    "Weight",
+]
+
+
+# standard library
+from dataclasses import dataclass
+from typing import Tuple
+
+
+# dependencies
+import numpy as np
+from xarray_dataclasses import Attr, Data
+
+
+# submodules
+from .dims import time, chan
+
+
+# dataclasses (time labels)
+@dataclass
+class Scan:
+    """Scan label."""
+
+    data: Data[time, np.str_]
+    long_name: Attr[str] = "Scan label"
+    short_name: Attr[str] = "scan"
+
+
+@dataclass
+class Mode:
+    """Mode label."""
+
+    data: Data[time, np.str_]
+    long_name: Attr[str] = "Mode label"
+    short_name: Attr[str] = "mode"
+
+
+# dataclasses (time observables)
+@dataclass
+class Exposure:
+    """Exposure time."""
+
+    data: Data[time, np.float64]
+    long_name: Attr[str] = "Exposure time"
+    short_name: Attr[str] = "exposure"
+    units: Attr[str] = "s"
+
+
+@dataclass
+class Interval:
+    """Interval time."""
+
+    data: Data[time, np.float64]
+    long_name: Attr[str] = "Interval time"
+    short_name: Attr[str] = "interval"
+    units: Attr[str] = "s"
+
+
+@dataclass
+class Longitude:
+    """Sky longitude."""
+
+    data: Data[time, np.float64]
+    long_name: Attr[str] = "Sky longitude" ""
+    short_name: Attr[str] = "longitude"
+    units: Attr[str] = "deg"
+
+
+@dataclass
+class Latitude:
+    """Sky latitude."""
+
+    data: Data[time, np.float64]
+    long_name: Attr[str] = "Sky latitude"
+    short_name: Attr[str] = "latitude"
+    units: Attr[str] = "deg"
+
+
+# dataclasses (chan labels)
+@dataclass
+class Beam:
+    """Beam label."""
+
+    data: Data[chan, np.str_]
+    long_name: Attr[str] = "Beam label"
+    short_name: Attr[str] = "beam"
+
+
+@dataclass
+class SpW:
+    """Spectral window label."""
+
+    data: Data[chan, np.str_]
+    long_name: Attr[str] = "Spectral window label"
+    short_name: Attr[str] = "spw"
+
+
+@dataclass
+class Pol:
+    """Polarization label."""
+
+    data: Data[chan, np.str_]
+    long_name: Attr[str] = "Polarization label"
+    short_name: Attr[str] = "pol"
+
+
+# dataclasses (chan observables)
+@dataclass
+class CenterFreq:
+    """Center frequency."""
+
+    data: Data[chan, np.float64]
+    long_name: Attr[str] = "Center frequency"
+    short_name: Attr[str] = "center_freq"
+    units: Attr[str] = "Hz"
+
+
+@dataclass
+class RefFreq:
+    """Reference frequency."""
+
+    data: Data[chan, np.float64]
+    long_name: Attr[str] = "Reference frequency"
+    short_name: Attr[str] = "ref_freq"
+    units: Attr[str] = "Hz"
+
+
+@dataclass
+class Resolution:
+    """Spectral resolution."""
+
+    data: Data[chan, np.float64]
+    long_name: Attr[str] = "Spectral resolution"
+    short_name: Attr[str] = "resolution"
+    units: Attr[str] = "Hz"
+
+
+@dataclass
+class Width:
+    """Channel width."""
+
+    data: Data[chan, np.float64]
+    long_name: Attr[str] = "Channel width"
+    short_name: Attr[str] = "width"
+    units: Attr[str] = "Hz"
+
+
+@dataclass
+class LonOffset:
+    """Offset from sky longitude."""
+
+    data: Data[chan, np.float64]
+    long_name: Attr[str] = "Offset from sky longitude"
+    short_name: Attr[str] = "lon_offset"
+    units: Attr[str] = "deg"
+
+
+@dataclass
+class LatOffset:
+    """Offset from sky latitude."""
+
+    data: Data[chan, np.float64]
+    long_name: Attr[str] = "Offset from sky latitude"
+    short_name: Attr[str] = "lat_offset"
+    units: Attr[str] = "deg"
+
+
+# dataclasses (time-chan observables)
+@dataclass
+class Mask:
+    """Mask for an array."""
+
+    data: Data[Tuple[time, chan], np.bool_]
+    long_name: Attr[str] = "Mask for an array"
+    short_name: Attr[str] = "mask"
+
+
+@dataclass
+class Sigma:
+    """Uncertainty of an array."""
+
+    data: Data[Tuple[time, chan], np.float64]
+    long_name: Attr[str] = "Uncertainty of an array"
+    short_name: Attr[str] = "sigma"
+
+
+@dataclass
+class Weight:
+    """Weight for an array."""
+
+    data: Data[Tuple[time, chan], np.float64]
+    long_name: Attr[str] = "Weight for an array"
+    short_name: Attr[str] = "weight"
diff --git a/sdarray/dims.py b/sdarray/dims.py
index 427a17d..3737f26 100644
--- a/sdarray/dims.py
+++ b/sdarray/dims.py
@@ -6,29 +6,33 @@ from dataclasses import dataclass
 
 
 # dependencies
+import numpy as np
 from typing_extensions import Literal
 from xarray_dataclasses import Attr, Data
 
 
 # constants
 time = Literal["time"]
+"""Type hint for the time axis."""
+
 chan = Literal["chan"]
+"""Type hint for the channel axis."""
 
 
 # dataclasses
 @dataclass
 class Time:
-    """Time in UTC."""
+    """Start time in UTC."""
 
-    data: Data[time, Literal["M8[ns]"]]
-    long_name: Attr[str] = "Time in UTC"
+    data: Data[time, np.datetime64]
+    long_name: Attr[str] = "Start time in UTC"
     short_name: Attr[str] = "time"
 
 
 @dataclass
 class Chan:
-    """Channel ID."""
+    """Channel number."""
 
-    data: Data[chan, Literal["i8"]]
-    long_name: Attr[str] = "Channel ID"
+    data: Data[chan, np.int64]
+    long_name: Attr[str] = "Channel number"
     short_name: Attr[str] = "chan"


Test patch:
diff --git a/tests/test_coords.py b/tests/test_coords.py
new file mode 100644
index 0000000..aa329e6
--- /dev/null
+++ b/tests/test_coords.py
@@ -0,0 +1,200 @@
+# dependencies
+import numpy as np
+import sdarray as sd
+from xarray_dataclasses import asdataarray
+
+
+# constants
+SHORT_NAME = "short_name"
+UNITS = "units"
+
+
+# test functions
+def test_scan() -> None:
+    data = np.array(["0"], np.str_)
+    array = asdataarray(sd.coords.Scan(data))
+
+    assert array.data == data
+    assert array.dims == ("time",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "scan"
+
+
+def test_mode() -> None:
+    data = np.array(["0"], np.str_)
+    array = asdataarray(sd.coords.Mode(data))
+
+    assert array.data == data
+    assert array.dims == ("time",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "mode"
+
+
+def test_exposure() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.Exposure(data))
+
+    assert array.data == data
+    assert array.dims == ("time",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "exposure"
+    assert array.attrs[UNITS] == "s"
+
+
+def test_interval() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.Interval(data))
+
+    assert array.data == data
+    assert array.dims == ("time",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "interval"
+    assert array.attrs[UNITS] == "s"
+
+
+def test_longitude() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.Longitude(data))
+
+    assert array.data == data
+    assert array.dims == ("time",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "longitude"
+    assert array.attrs[UNITS] == "deg"
+
+
+def test_latitude() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.Latitude(data))
+
+    assert array.data == data
+    assert array.dims == ("time",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "latitude"
+    assert array.attrs[UNITS] == "deg"
+
+
+def test_beam() -> None:
+    data = np.array(["0"], np.str_)
+    array = asdataarray(sd.coords.Beam(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "beam"
+
+
+def test_spw() -> None:
+    data = np.array(["0"], np.str_)
+    array = asdataarray(sd.coords.SpW(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "spw"
+
+
+def test_pol() -> None:
+    data = np.array(["0"], np.str_)
+    array = asdataarray(sd.coords.Pol(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "pol"
+
+
+def test_center_freq() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.CenterFreq(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "center_freq"
+    assert array.attrs[UNITS] == "Hz"
+
+
+def test_ref_freq() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.RefFreq(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "ref_freq"
+    assert array.attrs[UNITS] == "Hz"
+
+
+def test_resolution() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.Resolution(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "resolution"
+    assert array.attrs[UNITS] == "Hz"
+
+
+def test_width() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.Width(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "width"
+    assert array.attrs[UNITS] == "Hz"
+
+
+def test_lon_offset() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.LonOffset(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "lon_offset"
+    assert array.attrs[UNITS] == "deg"
+
+
+def test_lat_offset() -> None:
+    data = np.array([0.0], np.float64)
+    array = asdataarray(sd.coords.LatOffset(data))
+
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "lat_offset"
+    assert array.attrs[UNITS] == "deg"
+
+
+def test_mask() -> None:
+    data = np.array([[False]], np.bool_)
+    array = asdataarray(sd.coords.Mask(data))
+
+    assert array.data == data
+    assert array.dims == ("time", "chan")
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "mask"
+
+
+def test_sigma() -> None:
+    data = np.array([[0.0]], np.float64)
+    array = asdataarray(sd.coords.Sigma(data))
+
+    assert array.data == data
+    assert array.dims == ("time", "chan")
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "sigma"
+
+
+def test_weight() -> None:
+    data = np.array([[1.0]], np.float64)
+    array = asdataarray(sd.coords.Weight(data))
+
+    assert array.data == data
+    assert array.dims == ("time", "chan")
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "weight"
diff --git a/tests/test_dims.py b/tests/test_dims.py
index 9681244..a38e088 100644
--- a/tests/test_dims.py
+++ b/tests/test_dims.py
@@ -4,20 +4,26 @@ import sdarray as sd
 from xarray_dataclasses import asdataarray
 
 
+# constants
+SHORT_NAME = "short_name"
+
+
 # test functions
 def test_time() -> None:
-    data = np.array(["2000-01-01"], "M8[ns]")
-    time = asdataarray(sd.dims.Time(data))
+    data = np.array(["2000-01-01"], np.datetime64)
+    array = asdataarray(sd.dims.Time(data))
 
-    assert time.data == data
-    assert time.dims == ("time",)
-    assert time.dtype == data.dtype
+    assert array.data == data
+    assert array.dims == ("time",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "time"
 
 
 def test_chan() -> None:
-    data = np.array([0], "i8")
-    chan = asdataarray(sd.dims.Chan(data))
+    data = np.array([0], np.int64)
+    array = asdataarray(sd.dims.Chan(data))
 
-    assert chan.data == data
-    assert chan.dims == ("chan",)
-    assert chan.dtype == data.dtype
+    assert array.data == data
+    assert array.dims == ("chan",)
+    assert array.dtype.type == data.dtype.type
+    assert array.attrs[SHORT_NAME] == "chan"