Commit d9770c2d authored by dboe's avatar dboe
Browse files

fixing tests and changing base_vector in grid to Tensor for coord_sys attached.

parent 2fac8925
Pipeline #107378 passed with stage
in 27 seconds
# pylint:disable=missing_function_docstring
import numpy as np
from tempfile import NamedTemporaryFile
# pylint:disable=missing-function-docstring,missing-module-docstring,missing-class-docstring,invalid-name,protected-access
import pickle
import pathlib
import unittest
import tfields
import uuid
from tempfile import NamedTemporaryFile
from copy import deepcopy
import numpy as np
import tfields
ATOL = 1e-8
# pylint:disable=no-member
class Base_Check(object):
class Base_Check:
def demand_equal(self, other):
self.assertIsInstance(other, type(self._inst))
......@@ -29,8 +31,6 @@ class Base_Check(object):
self.demand_equal(reloaded)
def test_deep_copy(self):
from copy import deepcopy
other = deepcopy(self._inst)
self.demand_deep_copy(other)
......@@ -104,6 +104,7 @@ class Tensors_Check(AbstractNdarray_Check):
tensors = np.array(self._inst)
if len(self._inst) > 0:
# pylint:disable=unsubscriptable-object
item = self._inst[index]
self.assertTrue(np.array_equal(item, tensors[index]))
self.assertIsInstance(item, check_type)
......@@ -181,15 +182,18 @@ class TensorFields_Check(Tensors_Check):
self.check_fields_equal(self._inst.fields, self._fields)
def check_fields_equal(self, fields_a, fields_b):
for field, target_field in zip(self._inst.fields, self._fields):
for field, target_field in zip(fields_a, fields_b):
self.assertTrue(np.array_equal(field, target_field))
# fields are copied not reffered by a pointer
self.assertFalse(field is target_field)
# The below test is purposely disabled. It is better as a philosphy to not leave
# setitem values mutable.
# # fields are copied not reffered by a pointer
# self.assertFalse(field is target_field)
def demand_index_equal(self, index, check_type):
super().demand_index_equal(index, check_type)
if len(self._inst) > 0:
# pylint:disable=unsubscriptable-object
item = self._inst[index]
for i, field in enumerate(self._inst.fields):
if check_type == "type":
......@@ -221,7 +225,7 @@ class TensorFields_Check(Tensors_Check):
def test_field_name_getitem(self):
if self._inst.fields:
for i, field in enumerate(self._inst.fields):
for field in self._inst.fields:
if field.name is not None:
self.assertTrue(self._inst.fields[field.name].equal(field))
......@@ -234,6 +238,7 @@ class TensorFields_Check(Tensors_Check):
name = field.name
else:
name = str(uuid.uuid4())
field.name = name
# setitem via fields
self._inst.fields[name] = field
......@@ -266,9 +271,9 @@ class TensorMaps_Check(TensorFields_Check):
self.assertIsNot(self._inst.maps[i], other.maps[i])
"""
EMPTY TESTS
"""
#############
# EMPTY TESTS
#############
class Tensors_Empty_Test(Tensors_Check, unittest.TestCase):
......
# pylint:disable=missing-function-docstring,missing-module-docstring,missing-class-docstring,invalid-name
import unittest
import itertools
import numpy as np
......@@ -43,7 +44,7 @@ class TensorGrid_Test(unittest.TestCase, TensorFields_Check):
def check_filled(self, tg):
self.assertTrue(tg.equal(self.res))
self.assertListEqual(tg.base_vectors, self.bv)
self.assertListEqual(list(tg.base_num_tuples()), self.bv)
def check_empty(self, tg):
self.assertTrue(tg.is_empty())
......@@ -62,9 +63,17 @@ class TensorGrid_Test(unittest.TestCase, TensorFields_Check):
*self.bv, iter_order=self.iter_order, coord_sys=tfields.bases.CYLINDER
)
self.assertEqual(tg.coord_sys, tfields.bases.CYLINDER)
self.assertEqual(tg.base_vectors.coord_sys, tfields.bases.CYLINDER)
tge = tg.explicit()
self.assertEqual(tge.coord_sys, tfields.bases.CYLINDER)
# transformation does not change the base_vector coord_sys
tg.transform(tfields.bases.CARTESIAN)
self.assertEqual(tg.coord_sys, tfields.bases.CARTESIAN)
self.assertEqual(tg.base_vectors.coord_sys, tfields.bases.CYLINDER)
tge = tg.explicit()
self.assertEqual(tge.coord_sys, tfields.bases.CARTESIAN)
def test_getitem(self):
tg = TensorGrid.empty(*self.bv, iter_order=self.iter_order)
self.check_filled(tg[:])
......@@ -117,9 +126,11 @@ class TensorGrid_Test_IO_Change(unittest.TestCase):
t2 = TensorGrid_Test_Permutation1()
t2.setUp()
# pylint:disable=protected-access
tg1 = t1._inst
tg1.fields.append(tfields.Tensors(tg1))
tg1_original = tg1.copy()
# pylint:disable=protected-access
tg2 = t2._inst
tg2.fields.append(tfields.Tensors(tg2))
......@@ -137,15 +148,12 @@ class TensorGrid_Test_IO_Change(unittest.TestCase):
def test_lib_multi(self):
bv_lengths = [2, 3, 4]
# pylint:disable=unused-variable
for iter_order in itertools.permutations([0, 1, 2]):
# pylint:disable=unused-variable
for new_iter_order in itertools.permutations([0, 1, 2]):
forw = grid.change_iter_order(bv_lengths, [0, 1, 2], [2, 0, 1])
back = grid.change_iter_order(bv_lengths, [2, 0, 1], [0, 1, 2])
self.assertTrue(
(np.array(forw)[back] == np.arange(np.prod(bv_lengths))).all()
)
if __name__ == "__main__":
TensorGrid_Test().test_tensor_grid()
# unittest.main()
......@@ -8,8 +8,8 @@ core of tfields library
contains numpy ndarray derived bases of the tfields package
Notes:
* It could be worthwhile concidering [np.li.mixins.NDArrayOperatorsMixin]<https://
docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.lib.mixins.NDArrayOperatorsMixin.html>
# noqa:E501 pylint:disable=line-too-long,
* It could be worthwhile concidering `np.li.mixins.NDArrayOperatorsMixin <https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.lib.mixins.NDArrayOperatorsMixin.html>`_
TODO:
* lint the docstrings!!!
......@@ -59,8 +59,8 @@ class AbstractObject(rna.polymorphism.Storable):
Abstract base class for all tfields objects implementing polymorphisms
TODO:
* Use abstract base class to define the polymorphism contract:
see https://stackoverflow.com/questions/3570796/why-use-abstract-base-classes-in-python
# noqa:E501 pylint:disable=line-too-long,
* Use abstract base class to define the polymorphism contract: see `here <https://stackoverflow.com/questions/3570796/why-use-abstract-base-classes-in-python>`_
"""
def _save_npz(self, path):
......@@ -175,7 +175,7 @@ class AbstractObject(rna.polymorphism.Storable):
return cls._from_dict_legacy(**content)
here = {}
for string in content: # TOO no sortelist
for string in content: # TODO no sortelist
value = content[string]
attr, _, end = string.partition(cls._HIERARCHY_SEPARATOR)
......@@ -187,14 +187,16 @@ class AbstractObject(rna.polymorphism.Storable):
here[attr][key][end] = value
# Do the recursion
# pylint:disable=consider-using-dict-items
for attr in here:
# pylint:disable=consider-using-dict-items
for key in here[attr]:
if "type" in here[attr][key]:
obj_type = here[attr][key].get("type")
if isinstance(obj_type, np.ndarray): # happens on np.load
obj_type = obj_type.tolist()
if isinstance(obj_type, bytes):
# asthonishingly, this is not necessary under linux.
# astonishingly, this is not necessary under linux.
# Found under nt. ???
obj_type = obj_type.decode("UTF-8")
obj_type = getattr(tfields, obj_type)
......@@ -410,7 +412,7 @@ class AbstractNdarray(np.ndarray, AbstractObject):
# Create our own tuple to pass to __setstate__
new_state = pickled_state[2] + tuple(
[getattr(self, slot) for slot in self._iter_slots()]
getattr(self, slot) for slot in self._iter_slots()
)
# Return a tuple that replaces the parent's __setstate__
......@@ -674,6 +676,7 @@ class Tensors(AbstractNdarray): # pylint: disable=too-many-public-methods
cls._update_slot_kwargs(kwargs)
# set kwargs to slots attributes
# pylint:disable=consider-using-dict-items
for attr in kwargs:
if attr not in cls._iter_slots():
raise AttributeError(
......@@ -1604,6 +1607,7 @@ class Tensors(AbstractNdarray): # pylint: disable=too-many-public-methods
return (evecs * evalfs.T).T
@property
# pylint:disable=invalid-name
def t(self):
"""
Same as self.T but for tensor dimension only. Keeping the order of stacked tensors.
......@@ -1615,13 +1619,15 @@ class Tensors(AbstractNdarray): # pylint: disable=too-many-public-methods
"""
return self.transpose(0, *range(self.ndim)[-1:0:-1])
def dot(self, other):
def dot(self, b, out=None):
# pylint:disable=line-too-long
"""
Computes the n-d dot product between self and other defined as in mathematica
(reference.wolfram.com/legacy/v5/Built-inFunctions/AdvancedDocumentation/LinearAlgebra/2.7.html)
Computes the n-d dot product between self and other defined as in
`mathematica <https://reference.wolfram.com/legacy/v5/Built-inFunctions/
AdvancedDocumentation/LinearAlgebra/2.7.html>`_
by summing over the last dimension.
When self and other are both one-dimensional vectors, this is just the "usual" dot product;
when self and other are 2D matrices, this is matrix multiplication.
When self and b are both one-dimensional vectors, this is just the "usual" dot product;
when self and b are 2D matrices, this is matrix multiplication.
Examples:
>>> import tfields
......@@ -1636,8 +1642,11 @@ class Tensors(AbstractNdarray): # pylint: disable=too-many-public-methods
To get the angle between a and b you now just need
>>> angle = np.arccos(c)
"""
return Tensors(np.einsum("t...i,t...i->t...", self, other))
if out is not None:
raise NotImplementedError("performance feature 'out' not yet implemented")
return Tensors(np.einsum("t...i,t...i->t...", self, b))
# pylint:disable=redefined-builtin
def norm(self, ord=None, axis=None, keepdims=False):
"""
Calculate the norm up to rank 2
......@@ -1819,7 +1828,7 @@ class TensorFields(Tensors):
if rigid:
olen = len(obj)
field_lengths = [len(f) for f in obj.fields]
if not all([flen == olen for flen in field_lengths]):
if not all(flen == olen for flen in field_lengths):
raise ValueError(
"Length of base ({olen}) should be the same as"
" the length of all fields ({field_lengths}).".format(**locals())
......@@ -1928,7 +1937,7 @@ class TensorFields(Tensors):
@classmethod
def merged(cls, *objects, **kwargs):
if not all([isinstance(o, cls) for o in objects]):
if not all(isinstance(o, cls) for o in objects):
# Note: could allow if all map_fields are none
raise TypeError(
"Merge constructor only accepts {cls} instances."
......@@ -1946,7 +1955,7 @@ class TensorFields(Tensors):
inst, templates = (return_value, None)
fields = []
if all([len(obj.fields) == len(objects[0].fields) for obj in objects]):
if all(len(obj.fields) == len(objects[0].fields) for obj in objects):
for fld_idx in range(len(objects[0].fields)):
field = tfields.Tensors.merged(
*[obj.fields[fld_idx] for obj in objects]
......@@ -2072,6 +2081,10 @@ class TensorFields(Tensors):
class AbstractFields(list, AbstractObject):
"""
Extension of the list to tfields polymorphism. Allow setitem and getitem by object name.
"""
def _args(self):
return super()._args() + tuple(self)
......@@ -2081,7 +2094,10 @@ class AbstractFields(list, AbstractObject):
raise TypeError(
f"Value type {type(value)} does not support the 'name' field"
)
value.name = index
# We could set value.name = index but that would be very confusing since we do not copy
assert (
value.name == index
), "We do not dynamically want to change the name of a tensor"
for i, field in enumerate(self):
if hasattr(field, "name") and field.name == index:
......@@ -2095,7 +2111,7 @@ class AbstractFields(list, AbstractObject):
def __getitem__(self, index):
if isinstance(index, str):
for i, field in enumerate(self):
for field in self:
if hasattr(field, "name") and field.name == index:
return field
return super().__getitem__(index)
......@@ -2282,6 +2298,7 @@ class Maps(sortedcontainers.SortedDict, AbstractObject):
"""
if not self.keys() == other.keys():
return False
# pylint:disable=consider-using-dict-items
for dimension in self.keys():
if not self[dimension].equal(other[dimension], **kwargs):
return False
......@@ -2431,7 +2448,7 @@ class TensorMaps(TensorFields):
def merged(
cls, *objects, **kwargs
): # pylint: disable=too-many-locals,too-many-branches
if not all([isinstance(o, cls) for o in objects]):
if not all(isinstance(o, cls) for o in objects):
# Note: could allow if all face_fields are none
raise TypeError(
"Merge constructor only accepts {cls} instances.".format(**locals())
......@@ -2916,7 +2933,7 @@ class TensorMaps(TensorFields):
scalars_demanded = (
"color" not in kwargs
and "facecolors" not in kwargs
and any([v in kwargs for v in ["vmin", "vmax", "cmap"]])
and any(v in kwargs for v in ["vmin", "vmax", "cmap"])
)
map_ = self.maps[kwargs.pop("map", 3)]
map_index = kwargs.pop("map_index", None if not scalars_demanded else 0)
......
......@@ -21,7 +21,10 @@ def ensure_complex(*base_vectors) -> typing.List[typing.Tuple[float, float, comp
entry = entry.real
if j == 2:
# n_steps mapping to complex
entry = complex(entry)
if not isinstance(entry, complex):
entry = complex(0, entry)
elif entry.imag == 0:
entry = complex(0, entry.real)
new_vector.append(entry)
base_vectors[i] = tuple(new_vector)
return base_vectors
......
......@@ -3,7 +3,7 @@ Implementaiton of TensorGrid class
"""
import numpy as np
from .lib import grid, util
from .core import TensorFields
from .core import Tensors, TensorFields
class TensorGrid(TensorFields):
......@@ -14,11 +14,12 @@ class TensorGrid(TensorFields):
Args:
*base_vectors (tuple): indices of the axes which should be iterated
**kwargs:
num (np.array): same as np.linspace 'num'
iter_order (np.array): index order of building the grid.
further: see TensorFields class
"""
__slots__ = ["coord_sys", "name", "fields", "base_vectors", "iter_order"]
__slots__ = ["coord_sys", "name", "fields", "base_vectors", "num", "iter_order"]
__slot_setters__ = TensorFields.__slot_setters__ + [
None,
None,
......@@ -27,17 +28,48 @@ class TensorGrid(TensorFields):
def __new__(cls, tensors, *fields, **kwargs):
if issubclass(type(tensors), TensorGrid):
default_base_vectors = tensors.base_vectors
default_num = tensors.num
default_iter_order = tensors.iter_order
else:
default_base_vectors = kwargs.pop("base_vectors", None)
default_iter_order = np.arange(len(default_base_vectors))
base_vectors = [
tuple(bv) for bv in kwargs.pop("base_vectors", default_base_vectors)
]
base_vectors = grid.ensure_complex(*base_vectors)
default_num = None
default_iter_order = None
base_vectors = kwargs.pop("base_vectors", default_base_vectors)
num = kwargs.pop("num", default_num)
iter_order = kwargs.pop("iter_order", default_iter_order)
obj = super(TensorGrid, cls).__new__(cls, tensors, *fields, **kwargs)
if (
isinstance(base_vectors, tuple)
and base_vectors
and len(base_vectors[0]) == 3
):
base_vectors = grid.ensure_complex(*base_vectors)
if num is None:
num = np.array([int(bv[2].imag) for bv in base_vectors], dtype=int)
base_vectors = np.transpose([[bv[0], bv[1]] for bv in base_vectors])
# base_vectors
base_vectors = Tensors(base_vectors, coord_sys=obj.coord_sys)
if len(base_vectors) != 2:
raise ValueError(
f"base_vectors must be of lenght 2. Lenght is {len(base_vectors)}."
)
obj.base_vectors = base_vectors
# num
if num is None:
num = np.array([1 for _ in range(base_vectors.dim)])
else:
num = np.array(num, dtype=int)
obj.num = num
# iter_order
if iter_order is None:
iter_order = np.arange(base_vectors.dim)
else:
iter_order = np.array(iter_order, dtype=int)
obj.iter_order = iter_order
return obj
......@@ -51,6 +83,7 @@ class TensorGrid(TensorFields):
return item.__getitem__(index)
@classmethod
# pylint:disable=arguments-differ
def grid(cls, *base_vectors, tensors=None, fields=None, **kwargs):
"""
Build the grid (explicitly) from base vectors
......@@ -88,13 +121,22 @@ class TensorGrid(TensorFields):
base_vectors = obj.base_vectors
else:
if not all(
(a == b for a, b in zip(base_vectors, obj.base_vectors))
((a == b).all() for a, b in zip(base_vectors, obj.base_vectors))
):
raise NotImplementedError("Non-alligned base vectors")
kwargs.setdefault("base_vectors", base_vectors)
merge = super().merged(*objects, **kwargs)
return merge
def base_num_tuples(self):
"""
Returns the grid style base_vectors + num tuples
"""
return tuple(
(bv[0], bv[1], complex(0, n))
for bv, n in zip(self.base_vectors.T, self.num)
)
@property
def rank(self):
if self.is_empty():
......@@ -112,18 +154,24 @@ class TensorGrid(TensorFields):
"""
Build the grid explicitly (e.g. after changing base_vector, iter_order or init with empty)
"""
kwargs = {attr: getattr(self, attr) for attr in self.__slots__}
base_vectors = kwargs.pop("base_vectors")
return self.grid(*base_vectors, **kwargs)
kwargs = {
attr: getattr(self, attr)
for attr in self.__slots__
if attr not in ("base_vectors", "num", "coord_sys")
}
base_num_tuples = self.base_num_tuples()
kwargs["coord_sys"] = self.base_vectors.coord_sys
obj = self.grid(*base_num_tuples, **kwargs)
obj.transform(self.coord_sys)
return obj
def change_iter_order(self, iter_order):
"""
Transform the iter order
"""
bv_lengths = [int(bv[2].imag) for bv in self.base_vectors]
field_swap_indices = grid.change_iter_order(
# pylint:disable=access-member-before-definition
bv_lengths,
self.num,
self.iter_order,
iter_order,
)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment