Source code for pylife.mesh.gradient

# Copyright (c) 2019-2023 - for information on the respective copyright owner
# see the NOTICE file and/or the repository
# https://github.com/boschresearch/pylife
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__author__ = "Mustapha Kassem"
__maintainer__ = "Johannes Mueller"

import numpy as np
import pandas as pd

from .meshsignal import Mesh


[docs] @pd.api.extensions.register_dataframe_accessor('gradient') class Gradient(Mesh): '''Computes the gradient of a value in a 3D mesh Accesses a `mesh` registered in :mod:`meshsignal` Raises ------ AttributeError if at least one of the columns `x`, `y` is missing AttributeError if the index of the DataFrame is not a two level MultiIndex with the names `node_id` and `element_id` Notes ----- The gradient is calculated by fitting a plane into the nodes of each coordinate and the neighbor nodes using least square fitting. The method is described in a `thread on stackoverflow`_. .. _thread on stackoverflow: https://math.stackexchange.com/questions/2627946/how-to-approximate-numerically-the-gradient-of-the-function-on-a-triangular-mesh#answer-2632616 ''' def _find_neighbor(self): self.neighbors = {} for node, df in self._obj.groupby('node_id'): elidx = df.index.get_level_values('element_id') nodes = self._obj.loc[self._obj.index.isin(elidx, level='element_id')].index.get_level_values('node_id').to_numpy() self.neighbors[node] = np.setdiff1d(np.unique(nodes), [node]) def _calc_lst_sqr(self): self.lst_sqr_grad_dx = {} self.lst_sqr_grad_dy = {} self.lst_sqr_grad_dz = {} groups = self._obj.groupby('node_id') self._node_data = np.zeros((len(groups), 4), order='F') self._node_data[:, :3] = groups.first()[['x', 'y', 'z']] self._node_data[:, 3] = groups[self.value_key].mean() for node, row in zip(groups.first().index, np.nditer(self._node_data.T, flags=['external_loop'], order='F')): diff = self._node_data[self.neighbors[node]-1, :] - row dx, dy, dz = np.linalg.lstsq(diff[:, :3], diff[:, 3], rcond=None)[0] self.lst_sqr_grad_dx[node] = dx self.lst_sqr_grad_dy[node] = dy self.lst_sqr_grad_dz[node] = dz
[docs] def gradient_of(self, value_key): ''' returns the gradient Parameters ---------- value_key : str The key of the value that forms the gradient. Needs to be found in ``df`` Returns ------- gradient : pd.DataFrame A table describing the gradient indexed by ``node_id``. The keys for the components of the gradients are ``['d{value_key}_dx', 'd{value_key}_dy', 'd{value_key}_dz']``. ''' self.value_key = value_key self.nodes_id = np.unique(self._obj.index.get_level_values('node_id')) self._find_neighbor() self._calc_lst_sqr() df_grad = pd.DataFrame({'node_id': self.nodes_id, "d%s_dx" % value_key: [*self.lst_sqr_grad_dx.values()], "d%s_dy" % value_key: [*self.lst_sqr_grad_dy.values()], "d%s_dz" % value_key: [*self.lst_sqr_grad_dz.values()]}) df_grad = df_grad.set_index('node_id') return df_grad