Repository: ubermag/discretisedfield

Issue:
Remove `axesdict` and `raxesdict`

Discussion:

Patch:
diff --git a/discretisedfield/field.py b/discretisedfield/field.py
index f69e77f1..457254c3 100644
--- a/discretisedfield/field.py
+++ b/discretisedfield/field.py
@@ -1822,7 +1822,7 @@ class Field(_FieldIO):
         """
         d = {}
         for key, value in pad_width.items():
-            d[dfu.axesdict[key]] = value
+            d[self.mesh.region._dim2index(key)] = value
         padding_sequence = dfu.assemble_index((0, 0), len(self.array.shape), d)
 
         padded_array = np.pad(self.array, padding_sequence, mode=mode, **kwargs)
@@ -2609,7 +2609,14 @@ class Field(_FieldIO):
         points = list(self.mesh.line(p1=p1, p2=p2, n=n))
         values = [self(p) for p in points]
 
-        return df.Line(points=points, values=values)
+        return df.Line(
+            points=points,
+            values=values,
+            point_columns=self.mesh.region.dims,
+            value_columns=[f"v{dim}" for dim in self.vdims]
+            if self.vdims is not None
+            else "v",
+        )  # TODO scalar fields have no vdim
 
     def plane(self, *args, n=None, **kwargs):
         """Extracts field on the plane mesh.
@@ -3123,7 +3130,7 @@ class Field(_FieldIO):
         """
         mesh = self.mesh.attributes["realspace_mesh"]
         if self.mesh.attributes["isplane"] and not mesh.attributes["isplane"]:
-            mesh = mesh.plane(dfu.raxesdict[self.mesh.attributes["planeaxis"]])
+            mesh = mesh.plane(self.mesh.region.dims[self.mesh.attributes["planeaxis"]])
 
         values = []
         for idx in range(self.nvdim):
@@ -3171,7 +3178,7 @@ class Field(_FieldIO):
         """
         mesh = self.mesh.attributes["realspace_mesh"]
         if self.mesh.attributes["isplane"] and not mesh.attributes["isplane"]:
-            mesh = mesh.plane(dfu.raxesdict[self.mesh.attributes["planeaxis"]])
+            mesh = mesh.plane(self.mesh.region.dims[self.mesh.attributes["planeaxis"]])
 
         values = []
         for idx in range(self.nvdim):
@@ -3224,7 +3231,7 @@ class Field(_FieldIO):
         # TODO: Using PlaneMesh will simplify the code a lot here.
         mesh = df.Mesh(p1=p1, p2=p2, n=n)
         if self.mesh.attributes["isplane"]:
-            mesh = mesh.plane(dfu.raxesdict[self.mesh.attributes["planeaxis"]])
+            mesh = mesh.plane(self.mesh.region.dims[self.mesh.attributes["planeaxis"]])
 
         mesh.attributes["realspace_mesh"] = self.mesh
         mesh.attributes["fourierspace"] = True
diff --git a/discretisedfield/line.py b/discretisedfield/line.py
index bccff07a..cae99784 100644
--- a/discretisedfield/line.py
+++ b/discretisedfield/line.py
@@ -7,8 +7,6 @@ import pandas as pd
 import ubermagutil.typesystem as ts
 import ubermagutil.units as uu
 
-import discretisedfield.util as dfu
-
 
 @ts.typesystem(
     dim=ts.Scalar(expected_type=int, positive=True, const=True),
@@ -53,11 +51,11 @@ class Line:
 
     point_columns : list
 
-        Point column names. Defaults to None.
+        Point column names.
 
     value_columns : list
 
-        Value column names. Defaults to None.
+        Value column names.
 
     Raises
     ------
@@ -91,7 +89,7 @@ class Line:
 
     """
 
-    def __init__(self, points, values, point_columns=None, value_columns=None):
+    def __init__(self, points, values, point_columns, value_columns):
         if len(points) != len(values):
             msg = (
                 f"The number of points ({len(points)}) must be the same "
@@ -113,16 +111,14 @@ class Line:
 
         self.data = pd.DataFrame()
         self.data["r"] = np.linalg.norm(points - points[0, :], axis=1)
-        for i, column in enumerate(self.point_columns):
+        for i, column in enumerate(point_columns):
             self.data[column] = points[..., i]
-        for i, column in zip(range(values.shape[-1]), self.value_columns):
+        for i, column in zip(range(values.shape[-1]), value_columns):
             self.data[column] = values[..., i]
 
-        if point_columns is not None:
-            self.point_columns = point_columns
-
-        if value_columns is not None:
-            self.value_columns = value_columns
+        # TODO this should be done in the setter
+        self._point_columns = list(point_columns)
+        self._value_columns = list(value_columns)
 
     @property
     def point_columns(self):
@@ -166,10 +162,7 @@ class Line:
         Index(['r', 'p0', 'p1', 'p2', 'v'], dtype='object')
 
         """
-        if not hasattr(self, "_point_columns"):
-            return [f"p{i}" for i in dfu.axesdict.keys()]
-        else:
-            return self._point_columns
+        return self._point_columns
 
     @point_columns.setter
     def point_columns(self, val):
@@ -178,7 +171,7 @@ class Line:
             raise ValueError(msg)
 
         self.data = self.data.rename(dict(zip(self.point_columns, val)), axis=1)
-        self._point_columns = val
+        self._point_columns = list(val)
 
     @property
     def value_columns(self):
@@ -223,13 +216,7 @@ class Line:
         Index(['r', 'px', 'py', 'pz', 'my_interesting_value'], dtype='object')
 
         """
-        if not hasattr(self, "_value_columns"):
-            if self.dim == 1:
-                return ["v"]
-            else:
-                return [f"v{i}" for i in list(dfu.axesdict.keys())[: self.dim]]
-        else:
-            return self._value_columns
+        return self._value_columns
 
     @value_columns.setter
     def value_columns(self, val):
@@ -238,7 +225,7 @@ class Line:
             raise ValueError(msg)
 
         self.data = self.data.rename(dict(zip(self.value_columns, val)), axis=1)
-        self._value_columns = val
+        self._value_columns = list(val)
 
     @property
     def length(self):
diff --git a/discretisedfield/mesh.py b/discretisedfield/mesh.py
index 7924a0f8..ae171f6f 100644
--- a/discretisedfield/mesh.py
+++ b/discretisedfield/mesh.py
@@ -928,7 +928,7 @@ class Mesh(_MeshIO):
 
             # Only planeaxis is provided via args and the point is defined as
             # the center of the sample.
-            planeaxis = dfu.axesdict[args[0]]
+            planeaxis = self.region._dim2index(args[0])
             point = self.region.center[planeaxis]
         elif kwargs and not args:
             if len(kwargs) != 1:
@@ -936,7 +936,7 @@ class Mesh(_MeshIO):
                 raise ValueError(msg)
 
             planeaxis, point = list(kwargs.items())[0]
-            planeaxis = dfu.axesdict[planeaxis]
+            planeaxis = self.region._dim2index(planeaxis)
 
             # Check if point is outside the mesh region.
             test_point = list(self.region.center)  # make it mutable
@@ -950,7 +950,7 @@ class Mesh(_MeshIO):
 
         # Get indices of in-plane axes.
         axis1, axis2 = tuple(
-            filter(lambda val: val != planeaxis, dfu.axesdict.values())
+            filter(lambda val: val != planeaxis, range(self.region.ndim))
         )
 
         if n is None:
@@ -1743,7 +1743,7 @@ class Mesh(_MeshIO):
 
         """
         if isinstance(axis, str):
-            axis = dfu.axesdict[axis]
+            axis = self.region._dim2index(axis)
 
         if multiplier is None:
             multiplier = uu.si_multiplier(self.region.edges[axis])
@@ -1752,7 +1752,10 @@ class Mesh(_MeshIO):
         slider_max = self.index2point(np.subtract(self.n, 1))[axis]
         slider_step = self.cell[axis]
         if description is None:
-            description = f"{dfu.raxesdict[axis]} ({uu.rsi_prefixes[multiplier]}m)"
+            description = (
+                f"{self.region.dims[axis]} ({uu.rsi_prefixes[multiplier]}"
+                f"{self.region.units[axis]})"
+            )
 
         values = np.arange(slider_min, slider_max + 1e-20, slider_step)
         labels = np.around(values / multiplier, decimals=3)
@@ -1810,7 +1813,7 @@ class Mesh(_MeshIO):
             raise ValueError(msg)
 
         return widget_cls(
-            options=list(dfu.axesdict.keys()),
+            options=self.region.dims,
             value="z",
             description=description,
             disabled=False,
diff --git a/discretisedfield/plotting/k3d_field.py b/discretisedfield/plotting/k3d_field.py
index 745da61c..79bacf35 100644
--- a/discretisedfield/plotting/k3d_field.py
+++ b/discretisedfield/plotting/k3d_field.py
@@ -5,7 +5,6 @@ import numpy as np
 import ubermagutil.units as uu
 
 import discretisedfield.plotting.util as plot_util
-import discretisedfield.util as dfu
 
 
 class K3dField:
@@ -111,10 +110,6 @@ class K3dField:
         if multiplier is None:
             multiplier = uu.si_max_multiplier(self.data.mesh.region.edges)
 
-        unit = (
-            rf" ({uu.rsi_prefixes[multiplier]}" rf'{self.data.mesh.attributes["unit"]})'
-        )
-
         if interactive_field is not None:
             plot.camera_auto_fit = False
 
@@ -151,7 +146,12 @@ class K3dField:
             plot_array, color_map=color, bounds=bounds, outlines=False, **kwargs
         )
 
-        plot.axes = [i + r"\,\text{{{}}}".format(unit) for i in dfu.axesdict.keys()]
+        plot.axes = [
+            rf"dim\,\text{{{uu.rsi_prefixes[multiplier]}{unit}}}"
+            for dim, unit in zip(
+                self.data.mesh.region.dims, self.data.mesh.region.units
+            )
+        ]
 
     def scalar(
         self,
@@ -255,10 +255,6 @@ class K3dField:
         if multiplier is None:
             multiplier = uu.si_max_multiplier(self.data.mesh.region.edges)
 
-        unit = (
-            rf" ({uu.rsi_prefixes[multiplier]}" rf'{self.data.mesh.attributes["unit"]})'
-        )
-
         if interactive_field is not None:
             plot.camera_auto_fit = False
 
@@ -308,7 +304,12 @@ class K3dField:
             plot_array, color_map=cmap_int, bounds=bounds, outlines=False, **kwargs
         )
 
-        plot.axes = [i + r"\,\text{{{}}}".format(unit) for i in dfu.axesdict.keys()]
+        plot.axes = [
+            rf"dim\,\text{{{uu.rsi_prefixes[multiplier]}{unit}}}"
+            for dim, unit in zip(
+                self.data.mesh.region.dims, self.data.mesh.region.units
+            )
+        ]
 
     def vector(
         self,
@@ -439,10 +440,6 @@ class K3dField:
         if multiplier is None:
             multiplier = uu.si_max_multiplier(self.data.mesh.region.edges)
 
-        unit = (
-            rf" ({uu.rsi_prefixes[multiplier]}" rf'{self.data.mesh.attributes["unit"]})'
-        )
-
         if interactive_field is not None:
             plot.camera_auto_fit = False
 
@@ -517,7 +514,12 @@ class K3dField:
                 coordinates, color=plot_util.cp_int[0], point_size=point_size
             )
 
-        plot.axes = [i + r"\,\text{{{}}}".format(unit) for i in dfu.axesdict.keys()]
+        plot.axes = [
+            rf"dim\,\text{{{uu.rsi_prefixes[multiplier]}{unit}}}"
+            for dim, unit in zip(
+                self.data.mesh.region.dims, self.data.mesh.region.units
+            )
+        ]
 
     def __dir__(self):
         dirlist = dir(self.__class__)
diff --git a/discretisedfield/plotting/k3d_mesh.py b/discretisedfield/plotting/k3d_mesh.py
index 8bf9fc7e..41ef5262 100644
--- a/discretisedfield/plotting/k3d_mesh.py
+++ b/discretisedfield/plotting/k3d_mesh.py
@@ -3,7 +3,6 @@ import numpy as np
 import ubermagutil.units as uu
 
 import discretisedfield.plotting.util as plot_util
-import discretisedfield.util as dfu
 
 
 class K3dMesh:
@@ -73,8 +72,6 @@ class K3dMesh:
         if multiplier is None:
             multiplier = uu.si_max_multiplier(self.mesh.region.edges)
 
-        unit = f"({uu.rsi_prefixes[multiplier]}m)"
-
         plot_array = np.ones(tuple(reversed(self.mesh.n))).astype(np.uint8)
         plot_array[0, 0, -1] = 2  # mark the discretisation cell
 
@@ -91,7 +88,10 @@ class K3dMesh:
             plot_array, color_map=color, bounds=bounds, outlines=False, **kwargs
         )
 
-        plot.axes = [i + r"\,\text{{{}}}".format(unit) for i in dfu.axesdict.keys()]
+        plot.axes = [
+            rf"dim\,\text{{{uu.rsi_prefixes[multiplier]}{unit}}}"
+            for dim, unit in zip(self.mesh.region.dims, self.mesh.region.units)
+        ]
 
     def subregions(
         self, *, plot=None, color=plot_util.cp_int, multiplier=None, **kwargs
@@ -153,8 +153,6 @@ class K3dMesh:
         if multiplier is None:
             multiplier = uu.si_max_multiplier(self.mesh.region.edges)
 
-        unit = f"({uu.rsi_prefixes[multiplier]}m)"
-
         plot_array = np.zeros(self.mesh.n)
         for index in self.mesh.indices:
             for i, subregion in enumerate(self.mesh.subregions.values()):
@@ -178,4 +176,7 @@ class K3dMesh:
             plot_array, color_map=color, bounds=bounds, outlines=False, **kwargs
         )
 
-        plot.axes = [i + r"\,\text{{{}}}".format(unit) for i in dfu.axesdict.keys()]
+        plot.axes = [
+            rf"dim\,\text{{{uu.rsi_prefixes[multiplier]}{unit}}}"
+            for dim, unit in zip(self.mesh.region.dims, self.mesh.region.units)
+        ]
diff --git a/discretisedfield/plotting/k3d_region.py b/discretisedfield/plotting/k3d_region.py
index 28e25b71..f9eb9ed2 100644
--- a/discretisedfield/plotting/k3d_region.py
+++ b/discretisedfield/plotting/k3d_region.py
@@ -3,7 +3,6 @@ import numpy as np
 import ubermagutil.units as uu
 
 import discretisedfield.plotting.util as plot_util
-import discretisedfield.util as dfu
 
 
 class K3dRegion:
@@ -84,9 +83,6 @@ class K3dRegion:
 
     def _axis_labels(self, plot, multiplier):
         plot.axes = [
-            i
-            + r"\,\text{{{}}}".format(
-                f"({uu.rsi_prefixes[multiplier]}{self.region.units[dfu.axesdict[i]]})"
-            )
-            for i in dfu.axesdict.keys()
+            rf"dim\,\text{{{uu.rsi_prefixes[multiplier]}{unit}}}"
+            for dim, unit in zip(self.region.dims, self.region.units)
         ]
diff --git a/discretisedfield/plotting/mpl_field.py b/discretisedfield/plotting/mpl_field.py
index e0443f23..4fa473b5 100644
--- a/discretisedfield/plotting/mpl_field.py
+++ b/discretisedfield/plotting/mpl_field.py
@@ -8,7 +8,6 @@ from mpl_toolkits.axes_grid1 import make_axes_locatable
 
 import discretisedfield as df
 import discretisedfield.plotting.util as plot_util
-import discretisedfield.util as dfu
 from discretisedfield.plotting.mpl import Mpl, add_colorwheel
 
 
@@ -46,7 +45,7 @@ class MplField(Mpl):
 
         self.planeaxis = field.mesh.attributes["planeaxis"]
         self.planeaxis_point = {
-            dfu.raxesdict[
+            self.field.mesh.region.dims[
                 self.field.mesh.attributes["planeaxis"]
             ]: self.field.mesh.attributes["point"]
         }
@@ -152,7 +151,8 @@ class MplField(Mpl):
             vector_field = self.field
             scalar_field = getattr(self.field, self.field.vdims[self.planeaxis])
             scalar_kw.setdefault(
-                "colorbar_label", f"{dfu.raxesdict[self.planeaxis]}-component"
+                "colorbar_label",
+                f"{self.field.mesh.region.dims[self.planeaxis]}-component",
             )
 
         scalar_kw.setdefault("filter_field", self.field.norm)
@@ -819,8 +819,8 @@ class MplField(Mpl):
             rf" ({uu.rsi_prefixes[multiplier]}"
             rf'{self.field.mesh.attributes["unit"]})'
         )
-        ax.set_xlabel(dfu.raxesdict[self.axis1] + unit)
-        ax.set_ylabel(dfu.raxesdict[self.axis2] + unit)
+        ax.set_xlabel(self.field.mesh.region.dims[self.axis1] + unit)
+        ax.set_ylabel(self.field.mesh.region.dims[self.axis2] + unit)
 
     def _extent(self, multiplier):
         # TODO Requires refactoring of df.Mesh
diff --git a/discretisedfield/tools/tools.py b/discretisedfield/tools/tools.py
index ee56cc79..31d27615 100644
--- a/discretisedfield/tools/tools.py
+++ b/discretisedfield/tools/tools.py
@@ -124,7 +124,11 @@ def topological_charge_density(field, /, method="continuous"):
         return (
             1
             / (4 * np.pi)
-            * of.dot(of.diff(dfu.raxesdict[axis1]).cross(of.diff(dfu.raxesdict[axis2])))
+            * of.dot(
+                of.diff(field.mesh.region.dims[axis1]).cross(
+                    of.diff(field.mesh.region.dims[axis2])
+                )
+            )
         )
 
     elif method == "berg-luescher":
@@ -434,7 +438,7 @@ def neigbouring_cell_angle(field, /, direction, units="rad"):
         msg = f"Cannot compute spin angles for a field with {field.nvdim=}."
         raise ValueError(msg)
 
-    if direction not in dfu.axesdict.keys():
+    if direction not in field.mesh.region.dims:
         msg = f"Cannot compute spin angles for direction {direction=}."
         raise ValueError(msg)
 
diff --git a/discretisedfield/util/__init__.py b/discretisedfield/util/__init__.py
index 663d5601..03c00a2c 100644
--- a/discretisedfield/util/__init__.py
+++ b/discretisedfield/util/__init__.py
@@ -1,8 +1,1 @@
-from .util import (
-    array2tuple,
-    assemble_index,
-    axesdict,
-    bergluescher_angle,
-    raxesdict,
-    rescale_xarray,
-)
+from .util import array2tuple, assemble_index, bergluescher_angle, rescale_xarray
diff --git a/discretisedfield/util/util.py b/discretisedfield/util/util.py
index 02ad9427..e4b1e703 100644
--- a/discretisedfield/util/util.py
+++ b/discretisedfield/util/util.py
@@ -1,12 +1,8 @@
 import cmath
-import collections
 
 import numpy as np
 import ubermagutil.units as uu
 
-axesdict = collections.OrderedDict(x=0, y=1, z=2)
-raxesdict = {value: key for key, value in axesdict.items()}
-
 
 def array2tuple(array):
     return array.item() if array.size == 1 else tuple(array.tolist())


Test patch:
diff --git a/discretisedfield/tests/test_line.py b/discretisedfield/tests/test_line.py
index 4b5be297..71071efa 100644
--- a/discretisedfield/tests/test_line.py
+++ b/discretisedfield/tests/test_line.py
@@ -36,27 +36,17 @@ def check_line(line):
 
 class TestLine:
     def test_init(self):
-        # Scalar values
         points = [(0, 0, 0), (1, 0, 0), (2, 0, 0)]
         values = [-1, 2, -3]
-        line = df.Line(points=points, values=values)
+        line = df.Line(
+            points=points, values=values, point_columns=list("xyz"), value_columns=["a"]
+        )
         check_line(line)
 
         assert line.length == 2
         assert line.n == 3
         assert line.dim == 1
 
-        # Vector values
-        points = [(0, 0, 0), (1, 1, 1)]
-        values = [(0, 0, 1), (0, 1, 0)]
-
-        line = df.Line(points=points, values=values)
-        check_line(line)
-
-        assert abs(line.length - np.sqrt(3)) < 1e-12
-        assert line.n == 2
-        assert line.dim == 3
-
         # Setting the point and value columns.
         points = [(0, 0, 0), (1, 1, 1)]
         values = [(0, 0, 1), (0, 1, 0)]
@@ -98,13 +88,23 @@ class TestLine:
         points = [(0, 0, 0), (1, 0, 0)]
         values = [-1, 2, -3]
         with pytest.raises(ValueError):
-            line = df.Line(points=points, values=values)
+            line = df.Line(
+                points=points,
+                values=values,
+                point_columns=list("xyz"),
+                value_columns=["a"],
+            )
 
     def test_point_value_columns(self):
         # Scalar values
         points = [(0, 0, 0), (1, 0, 0), (2, 0, 0)]
         values = [-1, 2, -3]
-        line = df.Line(points=points, values=values)
+        line = df.Line(
+            points=points,
+            values=values,
+            point_columns=["px", "py", "pz"],
+            value_columns=["v"],
+        )
 
         assert line.point_columns == ["px", "py", "pz"]
         assert line.value_columns == ["v"]
@@ -117,7 +117,12 @@ class TestLine:
         # Vector values.
         points = [(0, 0, 0), (1, 0, 0), (2, 0, 0)]
         values = [(0, 1, 3), (-1, 0, 0), (-2.13, 0, 0)]
-        line = df.Line(points=points, values=values)
+        line = df.Line(
+            points=points,
+            values=values,
+            point_columns=["px", "py", "pz"],
+            value_columns=["vx", "vy", "vz"],
+        )
 
         assert line.point_columns == ["px", "py", "pz"]
         assert line.value_columns == ["vx", "vy", "vz"]
@@ -130,7 +135,12 @@ class TestLine:
         # Exceptions
         points = [(0, 0, 0), (1, 0, 0), (2, 0, 0)]
         values = [(0, 1, 3), (-1, 0, 0), (-2.13, 0, 0)]
-        line = df.Line(points=points, values=values)
+        line = df.Line(
+            points=points,
+            values=values,
+            point_columns=["px", "py", "pz"],
+            value_columns=["v"],
+        )
 
         with pytest.raises(ValueError):
             line.point_columns = ["a", "b"]