From d118643bd140481dcc1054d12c45d1f224b8b170 Mon Sep 17 00:00:00 2001 From: FilippoOlivo Date: Wed, 2 Oct 2024 17:07:54 +0200 Subject: [PATCH 1/4] Add summation and remove deepcopy (only for tensors) in LabelTensor class --- pina/label_tensor.py | 66 +++++++++++++++++++++++++++++--------- tests/test_label_tensor.py | 35 ++++++++++++++++++-- 2 files changed, 84 insertions(+), 17 deletions(-) diff --git a/pina/label_tensor.py b/pina/label_tensor.py index 7646dd8a3..fe5ec77a6 100644 --- a/pina/label_tensor.py +++ b/pina/label_tensor.py @@ -36,13 +36,14 @@ def __init__(self, x, labels): """ self.labels = None + self.init_labels() if isinstance(labels, dict): - self.update_labels(labels) + self.update_labels_from_dict(labels) elif isinstance(labels, list): - self.init_labels_from_list(labels) + self.update_labels_from_list(labels) elif isinstance(labels, str): labels = [labels] - self.init_labels_from_list(labels) + self.update_labels_from_list(labels) else: raise ValueError(f"labels must be list, dict or string.") @@ -64,7 +65,7 @@ def extract(self, label_to_extract): if set(label_to_extract).issubset(last_dim_label) is False: raise ValueError('Cannot extract a dof which is not in the original LabelTensor') idx_to_extract = [last_dim_label.index(i) for i in label_to_extract] - new_tensor = deepcopy(self.tensor) + new_tensor = self.tensor new_tensor = new_tensor[..., idx_to_extract] new_labels = deepcopy(self.labels) last_dim_new_label = {self.tensor.ndim - 1: { @@ -74,7 +75,7 @@ def extract(self, label_to_extract): new_labels.update(last_dim_new_label) elif isinstance(label_to_extract, dict): new_labels = (deepcopy(self.labels)) - new_tensor = deepcopy(self.tensor) + new_tensor = self.tensor for k, v in label_to_extract.items(): idx_dim = None for kl, vl in self.labels.items(): @@ -85,7 +86,7 @@ def extract(self, label_to_extract): if isinstance(label_to_extract[k], (int, str)): label_to_extract[k] = [label_to_extract[k]] if set(label_to_extract[k]).issubset(dim_labels) is False: - raise ValueError('Cannot extract a dof which is not in the original labeltensor') + raise ValueError('Cannot extract a dof which is not in the original LabelTensor') idx_to_extract = [dim_labels.index(i) for i in label_to_extract[k]] indexer = [slice(None)] * idx_dim + [idx_to_extract] + [slice(None)] * (self.tensor.ndim - idx_dim - 1) new_tensor = new_tensor[indexer] @@ -184,20 +185,24 @@ def clone(self, *args, **kwargs): out = LabelTensor(super().clone(*args, **kwargs), self.labels) return out - def update_labels(self, labels): - """ - Update the internal label representation according to the values passed as input. - :param labels: The label(s) to update. - :type labels: dict - :raises ValueError: dof list contain duplicates or number of dof does not match with tensor shape - """ + def init_labels(self): self.labels = { idx_: { 'dof': range(self.tensor.shape[idx_]), 'name': idx_ } for idx_ in range(self.tensor.ndim) } + + def update_labels_from_dict(self, labels): + """ + Update the internal label representation according to the values passed as input. + + :param labels: The label(s) to update. + :type labels: dict + :raises ValueError: dof list contain duplicates or number of dof does not match with tensor shape + """ + tensor_shape = self.tensor.shape for k, v in labels.items(): if len(v['dof']) != len(set(v['dof'])): @@ -206,7 +211,7 @@ def update_labels(self, labels): raise ValueError('Number of dof does not match with tensor dimension') self.labels.update(labels) - def init_labels_from_list(self, labels): + def update_labels_from_list(self, labels): """ Given a list of dof, this method update the internal label representation @@ -214,4 +219,35 @@ def init_labels_from_list(self, labels): :type labels: list """ last_dim_labels = {self.tensor.ndim - 1: {'dof': labels, 'name': self.tensor.ndim - 1}} - self.update_labels(last_dim_labels) \ No newline at end of file + self.update_labels_from_dict(last_dim_labels) + + def update_labels(self, labels): + if hasattr(self, 'labels') is False: + self.init_labels() + if isinstance(labels, dict): + self.update_labels_from_dict(labels) + elif isinstance(labels, list): + self.update_labels_from_list(labels) + else: + raise ValueError('labels must be dict or list') + + @staticmethod + def summation(tensors): + if len(tensors) == 0: + raise ValueError('tensors list must not be empty') + if len(tensors) == 1: + return tensors[0] + labels = tensors[0].labels + for j in range(tensors[0].ndim): + for i in range(1, len(tensors)): + if labels[j] != tensors[i].labels[j]: + labels.pop(j) + break + + data = torch.zeros(tensors[0].tensor.shape) + for i in range(len(tensors)): + data += tensors[i].tensor + new_tensor = LabelTensor(data, labels) + return new_tensor + + diff --git a/tests/test_label_tensor.py b/tests/test_label_tensor.py index f87d3abb1..7f179f2ac 100644 --- a/tests/test_label_tensor.py +++ b/tests/test_label_tensor.py @@ -17,9 +17,10 @@ "dof": range(20) } } +labels_list = ['x', 'y', 'z'] labels_all = labels_column | labels_row -@pytest.mark.parametrize("labels", [labels_column, labels_row, labels_all]) +@pytest.mark.parametrize("labels", [labels_column, labels_row, labels_all, labels_list]) def test_constructor(labels): LabelTensor(data, labels) @@ -61,7 +62,6 @@ def test_extract_2D(labels_te): assert torch.all(torch.isclose(data[2,2].reshape(1, 1), new)) def test_extract_3D(): - labels = labels_all data = torch.rand(20, 3, 4) labels = { 1: { @@ -80,6 +80,7 @@ def test_extract_3D(): tensor = LabelTensor(data, labels) new = tensor.extract(labels_te) + tensor2 = LabelTensor(data, labels) assert new.ndim == tensor.ndim assert new.shape[0] == 20 assert new.shape[1] == 2 @@ -88,6 +89,10 @@ def test_extract_3D(): data[:, 0::2, 1:4].reshape(20, 2, 3), new )) + assert tensor2.ndim == tensor.ndim + assert tensor2.shape == tensor.shape + assert tensor.labels == tensor2.labels + assert new.shape != tensor.shape def test_concatenation_3D(): data_1 = torch.rand(20, 3, 4) @@ -146,3 +151,29 @@ def test_concatenation_3D(): assert lt_cat.labels[2]['dof'] == range(5) assert lt_cat.labels[0]['dof'] == range(20) assert lt_cat.labels[1]['dof'] == range(3) + + +def test_summation(): + lt1 = LabelTensor(torch.ones(20,3), labels_all) + lt2 = LabelTensor(torch.ones(30,3), ['x', 'y', 'z']) + with pytest.raises(RuntimeError): + LabelTensor.summation([lt1, lt2]) + lt1 = LabelTensor(torch.ones(20,3), labels_all) + lt2 = LabelTensor(torch.ones(20,3), labels_all) + lt_sum = LabelTensor.summation([lt1, lt2]) + assert lt_sum.ndim == lt_sum.ndim + assert lt_sum.shape[0] == 20 + assert lt_sum.shape[1] == 3 + assert lt_sum.labels == labels_all + assert torch.eq(lt_sum.tensor, torch.ones(20,3)*2).all() + lt1 = LabelTensor(torch.ones(20,3), labels_all) + lt2 = LabelTensor(torch.ones(20,3), labels_all) + lt3 = LabelTensor(torch.zeros(20, 3), labels_all) + lt_sum = LabelTensor.summation([lt1, lt2, lt3]) + assert lt_sum.ndim == lt_sum.ndim + assert lt_sum.shape[0] == 20 + assert lt_sum.shape[1] == 3 + assert lt_sum.labels == labels_all + assert torch.eq(lt_sum.tensor, torch.ones(20,3)*2).all() + + From d5e61ae597e11c838742a8c6d3875be578df59b8 Mon Sep 17 00:00:00 2001 From: FilippoOlivo Date: Thu, 3 Oct 2024 09:40:46 +0200 Subject: [PATCH 2/4] Update operators for compatibility with updated LabelTensor implementation --- pina/operators.py | 96 ++++++++++++++++++----------------------- tests/test_operators.py | 19 +++----- 2 files changed, 50 insertions(+), 65 deletions(-) diff --git a/pina/operators.py b/pina/operators.py index 17b45d814..4568a61d1 100644 --- a/pina/operators.py +++ b/pina/operators.py @@ -1,13 +1,13 @@ """ Module for operators vectorize implementation. Differential operators are used to write any differential problem. -These operators are implemented to work on different accellerators: CPU, GPU, TPU or MPS. +These operators are implemented to work on different accelerators: CPU, GPU, TPU or MPS. All operators take as input a tensor onto which computing the operator, a tensor with respect to which computing the operator, the name of the output variables to calculate the operator for (in case of multidimensional functions), and the variables name on which the operator is calculated. """ import torch - +from copy import deepcopy from pina.label_tensor import LabelTensor @@ -49,12 +49,12 @@ def grad_scalar_output(output_, input_, d): :rtype: LabelTensor """ - if len(output_.labels) != 1: + if len(output_.labels[output_.tensor.ndim-1]['dof']) != 1: raise RuntimeError("only scalar function can be differentiated") - if not all([di in input_.labels for di in d]): + if not all([di in input_.labels[input_.tensor.ndim-1]['dof'] for di in d]): raise RuntimeError("derivative labels missing from input tensor") - output_fieldname = output_.labels[0] + output_fieldname = output_.labels[output_.ndim-1]['dof'][0] gradients = torch.autograd.grad( output_, input_, @@ -65,41 +65,35 @@ def grad_scalar_output(output_, input_, d): retain_graph=True, allow_unused=True, )[0] - - gradients.labels = input_.labels + new_labels = deepcopy(input_.labels) + gradients.update_labels(new_labels) gradients = gradients.extract(d) - gradients.labels = [f"d{output_fieldname}d{i}" for i in d] - + new_labels[input_.tensor.ndim - 1]['dof'] = [f"d{output_fieldname}d{i}" for i in d] + gradients.update_labels(new_labels) return gradients if not isinstance(input_, LabelTensor): raise TypeError - if d is None: - d = input_.labels + d = input_.labels[input_.tensor.ndim-1]['dof'] if components is None: - components = output_.labels + components = output_.labels[output_.tensor.ndim-1]['dof'] - if output_.shape[1] == 1: # scalar output ################################ + if output_.shape[output_.ndim-1] == 1: # scalar output ################################ - if components != output_.labels: + if components != output_.labels[output_.tensor.ndim-1]['dof']: raise RuntimeError gradients = grad_scalar_output(output_, input_, d) - elif output_.shape[1] >= 2: # vector output ############################## - + elif output_.shape[output_.ndim-1] >= 2: # vector output ############################## + tensor_to_cat = [] for i, c in enumerate(components): c_output = output_.extract([c]) - if i == 0: - gradients = grad_scalar_output(c_output, input_, d) - else: - gradients = gradients.append( - grad_scalar_output(c_output, input_, d) - ) + tensor_to_cat.append(grad_scalar_output(c_output, input_, d)) + gradients = LabelTensor.cat(tensor_to_cat, dim=output_.tensor.ndim-1) else: raise NotImplementedError - return gradients @@ -130,27 +124,29 @@ def div(output_, input_, components=None, d=None): raise TypeError if d is None: - d = input_.labels + d = input_.labels[input_.tensor.ndim-1]['dof'] if components is None: - components = output_.labels + components = output_.labels[output_.tensor.ndim-1]['dof'] - if output_.shape[1] < 2 or len(components) < 2: + if output_.shape[output_.ndim-1] < 2 or len(components) < 2: raise ValueError("div supported only for vector fields") if len(components) != len(d): raise ValueError grad_output = grad(output_, input_, components, d) - div = torch.zeros(input_.shape[0], 1, device=output_.device) - labels = [None] * len(components) + last_dim_dof = [None] * len(components) + to_sum_tensors = [] for i, (c, d) in enumerate(zip(components, d)): c_fields = f"d{c}d{d}" - div[:, 0] += grad_output.extract(c_fields).sum(axis=1) - labels[i] = c_fields + last_dim_dof[i] = c_fields + to_sum_tensors.append(grad_output.extract(c_fields)) - div = div.as_subclass(LabelTensor) - div.labels = ["+".join(labels)] + div = LabelTensor.summation(to_sum_tensors) + new_labels = deepcopy(input_.labels) + new_labels[input_.tensor.ndim-1]['dof'] = ["+".join(last_dim_dof)] + div.update_labels(new_labels) return div @@ -177,10 +173,10 @@ def laplacian(output_, input_, components=None, d=None, method="std"): :rtype: LabelTensor """ if d is None: - d = input_.labels + d = input_.labels[input_.tensor.ndim-1]['dof'] if components is None: - components = output_.labels + components = output_.labels[output_.tensor.ndim-1]['dof'] if len(components) != len(d) and len(components) != 1: raise ValueError @@ -194,35 +190,29 @@ def laplacian(output_, input_, components=None, d=None, method="std"): if len(components) == 1: grad_output = grad(output_, input_, components=components, d=d) - result = torch.zeros(output_.shape[0], 1, device=output_.device) - for i, label in enumerate(grad_output.labels): + to_append_tensors = [] + for i, label in enumerate(grad_output.labels[grad_output.ndim-1]['dof']): gg = grad(grad_output, input_, d=d, components=[label]) - result[:, 0] += super(torch.Tensor, gg.T).__getitem__( - i - ) # TODO improve + to_append_tensors.append(gg.extract([gg.labels[gg.tensor.ndim-1]['dof'][i]])) labels = [f"dd{components[0]}"] - + result = LabelTensor.summation(tensors=to_append_tensors) + result.update_labels(labels) else: - result = torch.empty( - input_.shape[0], len(components), device=output_.device - ) labels = [None] * len(components) + to_append_tensors = [None] * len(components) for idx, (ci, di) in enumerate(zip(components, d)): - if not isinstance(ci, list): ci = [ci] if not isinstance(di, list): di = [di] - grad_output = grad(output_, input_, components=ci, d=di) - result[:, idx] = grad(grad_output, input_, d=di).flatten() - labels[idx] = f"dd{ci}dd{di}" - - result = result.as_subclass(LabelTensor) - result.labels = labels + to_append_tensors[idx] = grad(grad_output, input_, d=di) + labels[idx] = f"dd{ci[0]}dd{di[0]}" + result = LabelTensor.cat(tensors=to_append_tensors, dim=output_.tensor.ndim-1) + result.update_labels(labels) return result - +# TODO Fix advection operator def advection(output_, input_, velocity_field, components=None, d=None): """ Perform advection operation. The operator works for vectorial functions, @@ -244,10 +234,10 @@ def advection(output_, input_, velocity_field, components=None, d=None): :rtype: LabelTensor """ if d is None: - d = input_.labels + d = input_.labels[input_.tensor.ndim-1]['dof'] if components is None: - components = output_.labels + components = output_.labels[output_.tensor.ndim-1]['dof'] tmp = ( grad(output_, input_, components, d) diff --git a/tests/test_operators.py b/tests/test_operators.py index aa9ea9d8b..600dfa8a6 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -10,15 +10,14 @@ def func_vec(x): def func_scalar(x): - print('X') x_ = x.extract(['x']) y_ = x.extract(['y']) mu_ = x.extract(['mu']) return x_**2 + y_**2 + mu_**3 -data = torch.rand((20, 3), requires_grad=True) -inp = LabelTensor(data, ['x', 'y', 'mu']) +data = torch.rand((20, 3)) +inp = LabelTensor(data, ['x', 'y', 'mu']).requires_grad_(True) labels = ['a', 'b', 'c'] tensor_v = LabelTensor(func_vec(inp), labels) tensor_s = LabelTensor(func_scalar(inp).reshape(-1, 1), labels[0]) @@ -27,35 +26,31 @@ def func_scalar(x): def test_grad_scalar_output(): grad_tensor_s = grad(tensor_s, inp) assert grad_tensor_s.shape == inp.shape - assert grad_tensor_s.labels == [ - f'd{tensor_s.labels[0]}d{i}' for i in inp.labels + assert grad_tensor_s.labels[grad_tensor_s.ndim-1]['dof'] == [ + f'd{tensor_s.labels[tensor_s.ndim-1]["dof"][0]}d{i}' for i in inp.labels[inp.ndim-1]['dof'] ] grad_tensor_s = grad(tensor_s, inp, d=['x', 'y']) - assert grad_tensor_s.shape == (inp.shape[0], 2) - assert grad_tensor_s.labels == [ - f'd{tensor_s.labels[0]}d{i}' for i in ['x', 'y'] + assert grad_tensor_s.shape == (20, 2) + assert grad_tensor_s.labels[grad_tensor_s.ndim-1]['dof'] == [ + f'd{tensor_s.labels[tensor_s.ndim-1]["dof"][0]}d{i}' for i in ['x', 'y'] ] - def test_grad_vector_output(): grad_tensor_v = grad(tensor_v, inp) assert grad_tensor_v.shape == (20, 9) grad_tensor_v = grad(tensor_v, inp, d=['x', 'mu']) assert grad_tensor_v.shape == (inp.shape[0], 6) - def test_div_vector_output(): grad_tensor_v = div(tensor_v, inp) assert grad_tensor_v.shape == (20, 1) grad_tensor_v = div(tensor_v, inp, components=['a', 'b'], d=['x', 'mu']) assert grad_tensor_v.shape == (inp.shape[0], 1) - def test_laplacian_scalar_output(): laplace_tensor_v = laplacian(tensor_s, inp, components=['a'], d=['x', 'y']) assert laplace_tensor_v.shape == tensor_s.shape - def test_laplacian_vector_output(): laplace_tensor_v = laplacian(tensor_v, inp) assert laplace_tensor_v.shape == tensor_v.shape From fc8fd07175ae4f4a2381113628bfa143921132e5 Mon Sep 17 00:00:00 2001 From: FilippoOlivo Date: Fri, 4 Oct 2024 10:06:10 +0200 Subject: [PATCH 3/4] Implement labels.setter in LabelTensor class --- pina/label_tensor.py | 58 ++++++++++++++++++++++---------------- pina/operators.py | 10 +++---- tests/test_label_tensor.py | 1 + 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/pina/label_tensor.py b/pina/label_tensor.py index fe5ec77a6..f033f0995 100644 --- a/pina/label_tensor.py +++ b/pina/label_tensor.py @@ -35,8 +35,27 @@ def __init__(self, x, labels): {1: {"name": "space"['a', 'b', 'c']) """ - self.labels = None - self.init_labels() + self.labels = labels + + @property + def labels(self): + """Property decorator for labels + + :return: labels of self + :rtype: list + """ + return self._labels + + @labels.setter + def labels(self, labels): + """" + Set properly the parameter _labels + + :param labels: Labels to assign to the class variable _labels. + :type: labels: str | list(str) | dict + """ + if hasattr(self, 'labels') is False: + self.init_labels() if isinstance(labels, dict): self.update_labels_from_dict(labels) elif isinstance(labels, list): @@ -61,28 +80,28 @@ def extract(self, label_to_extract): if isinstance(label_to_extract, (str, int)): label_to_extract = [label_to_extract] if isinstance(label_to_extract, (tuple, list)): - last_dim_label = self.labels[self.tensor.ndim - 1]['dof'] + last_dim_label = self._labels[self.tensor.ndim - 1]['dof'] if set(label_to_extract).issubset(last_dim_label) is False: raise ValueError('Cannot extract a dof which is not in the original LabelTensor') idx_to_extract = [last_dim_label.index(i) for i in label_to_extract] new_tensor = self.tensor new_tensor = new_tensor[..., idx_to_extract] - new_labels = deepcopy(self.labels) + new_labels = deepcopy(self._labels) last_dim_new_label = {self.tensor.ndim - 1: { 'dof': label_to_extract, - 'name': self.labels[self.tensor.ndim - 1]['name'] + 'name': self._labels[self.tensor.ndim - 1]['name'] }} new_labels.update(last_dim_new_label) elif isinstance(label_to_extract, dict): - new_labels = (deepcopy(self.labels)) + new_labels = (deepcopy(self._labels)) new_tensor = self.tensor for k, v in label_to_extract.items(): idx_dim = None - for kl, vl in self.labels.items(): + for kl, vl in self._labels.items(): if vl['name'] == k: idx_dim = kl break - dim_labels = self.labels[idx_dim]['dof'] + dim_labels = self._labels[idx_dim]['dof'] if isinstance(label_to_extract[k], (int, str)): label_to_extract[k] = [label_to_extract[k]] if set(label_to_extract[k]).issubset(dim_labels) is False: @@ -92,7 +111,7 @@ def extract(self, label_to_extract): new_tensor = new_tensor[indexer] dim_new_label = {idx_dim: { 'dof': label_to_extract[k], - 'name': self.labels[idx_dim]['name'] + 'name': self._labels[idx_dim]['name'] }} new_labels.update(dim_new_label) else: @@ -105,7 +124,7 @@ def __str__(self): """ s = '' - for key, value in self.labels.items(): + for key, value in self._labels.items(): s += f"{key}: {value}\n" s += '\n' s += super().__str__() @@ -156,7 +175,7 @@ def cat(tensors, dim=0): def requires_grad_(self, mode=True): lt = super().requires_grad_(mode) - lt.labels = self.labels + lt.labels = self._labels return lt @property @@ -182,12 +201,12 @@ def clone(self, *args, **kwargs): :rtype: LabelTensor """ - out = LabelTensor(super().clone(*args, **kwargs), self.labels) + out = LabelTensor(super().clone(*args, **kwargs), self._labels) return out def init_labels(self): - self.labels = { + self._labels = { idx_: { 'dof': range(self.tensor.shape[idx_]), 'name': idx_ @@ -209,7 +228,7 @@ def update_labels_from_dict(self, labels): raise ValueError("dof must be unique") if len(v['dof']) != tensor_shape[k]: raise ValueError('Number of dof does not match with tensor dimension') - self.labels.update(labels) + self._labels.update(labels) def update_labels_from_list(self, labels): """ @@ -221,16 +240,6 @@ def update_labels_from_list(self, labels): last_dim_labels = {self.tensor.ndim - 1: {'dof': labels, 'name': self.tensor.ndim - 1}} self.update_labels_from_dict(last_dim_labels) - def update_labels(self, labels): - if hasattr(self, 'labels') is False: - self.init_labels() - if isinstance(labels, dict): - self.update_labels_from_dict(labels) - elif isinstance(labels, list): - self.update_labels_from_list(labels) - else: - raise ValueError('labels must be dict or list') - @staticmethod def summation(tensors): if len(tensors) == 0: @@ -250,4 +259,3 @@ def summation(tensors): new_tensor = LabelTensor(data, labels) return new_tensor - diff --git a/pina/operators.py b/pina/operators.py index 4568a61d1..58dcdff37 100644 --- a/pina/operators.py +++ b/pina/operators.py @@ -66,10 +66,10 @@ def grad_scalar_output(output_, input_, d): allow_unused=True, )[0] new_labels = deepcopy(input_.labels) - gradients.update_labels(new_labels) + gradients.labels = new_labels gradients = gradients.extract(d) new_labels[input_.tensor.ndim - 1]['dof'] = [f"d{output_fieldname}d{i}" for i in d] - gradients.update_labels(new_labels) + gradients.labels = new_labels return gradients if not isinstance(input_, LabelTensor): @@ -146,7 +146,7 @@ def div(output_, input_, components=None, d=None): div = LabelTensor.summation(to_sum_tensors) new_labels = deepcopy(input_.labels) new_labels[input_.tensor.ndim-1]['dof'] = ["+".join(last_dim_dof)] - div.update_labels(new_labels) + div.labels = new_labels return div @@ -196,7 +196,7 @@ def laplacian(output_, input_, components=None, d=None, method="std"): to_append_tensors.append(gg.extract([gg.labels[gg.tensor.ndim-1]['dof'][i]])) labels = [f"dd{components[0]}"] result = LabelTensor.summation(tensors=to_append_tensors) - result.update_labels(labels) + result.labels = labels else: labels = [None] * len(components) to_append_tensors = [None] * len(components) @@ -209,7 +209,7 @@ def laplacian(output_, input_, components=None, d=None, method="std"): to_append_tensors[idx] = grad(grad_output, input_, d=di) labels[idx] = f"dd{ci[0]}dd{di[0]}" result = LabelTensor.cat(tensors=to_append_tensors, dim=output_.tensor.ndim-1) - result.update_labels(labels) + result.labels = labels return result # TODO Fix advection operator diff --git a/tests/test_label_tensor.py b/tests/test_label_tensor.py index 7f179f2ac..1d143e1a8 100644 --- a/tests/test_label_tensor.py +++ b/tests/test_label_tensor.py @@ -24,6 +24,7 @@ def test_constructor(labels): LabelTensor(data, labels) + def test_wrong_constructor(): with pytest.raises(ValueError): LabelTensor(data, ['a', 'b']) From fd2cae5dcb364b0e3856d40f9b0e968af5d07ee8 Mon Sep 17 00:00:00 2001 From: FilippoOlivo Date: Fri, 4 Oct 2024 15:29:39 +0200 Subject: [PATCH 4/4] Update LabelTensor --- pina/label_tensor.py | 10 ++++++++++ tests/test_label_tensor.py | 24 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pina/label_tensor.py b/pina/label_tensor.py index f033f0995..08e0b03e6 100644 --- a/pina/label_tensor.py +++ b/pina/label_tensor.py @@ -259,3 +259,13 @@ def summation(tensors): new_tensor = LabelTensor(data, labels) return new_tensor + def last_dim_dof(self): + return self._labels[self.tensor.ndim - 1]['dof'] + + def append(self, tensor, mode='std'): + print(self.labels) + print(tensor.labels) + if mode == 'std': + new_label_tensor = LabelTensor.cat([self, tensor], dim=self.tensor.ndim - 1) + + return new_label_tensor diff --git a/tests/test_label_tensor.py b/tests/test_label_tensor.py index 1d143e1a8..6ef484f0b 100644 --- a/tests/test_label_tensor.py +++ b/tests/test_label_tensor.py @@ -177,4 +177,26 @@ def test_summation(): assert lt_sum.labels == labels_all assert torch.eq(lt_sum.tensor, torch.ones(20,3)*2).all() - +def test_append_3D(): + data_1 = torch.rand(20, 3, 4) + labels_1 = ['x', 'y', 'z', 'w'] + lt1 = LabelTensor(data_1, labels_1) + data_2 = torch.rand(50, 3, 4) + labels_2 = ['x', 'y', 'z', 'w'] + lt2 = LabelTensor(data_2, labels_2) + lt1 = lt1.append(lt2) + assert lt1.shape == (70, 3, 4) + assert lt1.labels[0]['dof'] == range(70) + assert lt1.labels[1]['dof'] == range(3) + assert lt1.labels[2]['dof'] == ['x', 'y', 'z', 'w'] + data_1 = torch.rand(20, 3, 2) + labels_1 = ['x', 'y'] + lt1 = LabelTensor(data_1, labels_1) + data_2 = torch.rand(20, 3, 2) + labels_2 = ['z', 'w'] + lt2 = LabelTensor(data_2, labels_2) + lt1 = lt1.append(lt2, mode='cross') + assert lt1.shape == (20, 3, 4) + assert lt1.labels[0]['dof'] == range(20) + assert lt1.labels[1]['dof'] == range(3) + assert lt1.labels[2]['dof'] == ['x', 'y', 'z', 'w']