Source code for pylife.materiallaws.notch_approximation_law

# 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__ = ["Benjamin Maier"]
__maintainer__ = __author__

from abc import ABC, abstractmethod

import numpy as np
from scipy import optimize
import pandas as pd

import pylife.materiallaws.rambgood

class NotchApproximationLawBase(ABC):
    """This is a base class for any notch approximation law, e.g., the extended Neuber and the Seeger-Beste laws.

    It initializes the internal variables used by the derived classes and provides getters and setters.
    """

    def __init__(self, E, K, n, K_p=None):
        self._E = E
        self._K = K
        self._n = n
        self._K_p = K_p

        self._ramberg_osgood_relation = pylife.materiallaws.rambgood.RambergOsgood(E, K, n)

    @property
    def E(self):
        """Young's Modulus"""
        return self._E

    @property
    def K(self):
        """the strength coefficient"""
        return self._K

    @property
    def n(self):
        """the strain hardening coefficient"""
        return self._n

    @property
    def K_p(self):
        """the shape factor (de: Traglastformzahl)"""
        return self._K_p

    @property
    def ramberg_osgood_relation(self):
        """the Ramberg-Osgood relation object, i.e., an object of type RambergOsgood
        """
        return self._ramberg_osgood_relation

    @K_p.setter
    def K_p(self, value):
        """Set the shape factor value K_p  (de: Traglastformzahl)"""
        self._K_p = value

    @K.setter
    def K_prime(self, value):
        """Set the strain hardening coefficient"""
        self._K = value
        self._ramberg_osgood_relation = pylife.materiallaws.rambgood.RambergOsgood(self._E, self._K, self._n)

    @K.setter
    def K(self, value):
        """Set the strain hardening coefficient"""
        self.K_prime = value

    @abstractmethod
    def load(self, stress, *, rtol=1e-4, tol=1e-4):
        """Apply the notch-approximation law "backwards", i.e., compute the linear-elastic stress (called "load" or "L" in FKM nonlinear)

        This is to be reimplemented by derived classes

        Parameters
        ----------
        stress : array-like float
            The elastic-plastic stress as computed by the notch approximation
        rtol : float, optional
            The relative tolerance to which the implicit formulation of the load gets solved,
            by default 1e-4
        tol : float, optional
            The absolute tolerance to which the implicit formulation of the load gets solved,
            by default 1e-4

        Returns
        -------
        load : array-like float
            The resulting load or lienar-elastic stress.

        """
        ...

    @abstractmethod
    def stress(self, load, *, rtol=1e-4, tol=1e-4):
        r"""Calculate the stress of the primary path in the stress-strain diagram at a given

        This is to be reimplemented by derived classes

        Parameters
        ----------
        load : array-like float
            The elastic von Mises stress from a linear elastic FEA.
            In the FKM nonlinear document, this is also called load "L", because it is derived
            from a load-time series. Note that this value is scaled to match the actual loading
            in the assessment, it equals the FEM solution times the transfer factor.
        rtol : float, optional
            The relative tolerance to which the implicit formulation of the stress gets solved,
            by default 1e-4
        tol : float, optional
            The absolute tolerance to which the implicit formulation of the stress gets solved,
            by default 1e-4

        Returns
        -------
        stress : array-like float
            The resulting elastic-plastic stress according to the notch-approximation law.
        """        "Compute the local notch stress from the local nominal load."
        ...

    @abstractmethod
    def strain(self, load):
        """Calculate the strain of the primary path in the stress-strain diagram at a given stress and load.

        This is to be reimplemented by derived classes

        Parameters
        ----------
        stress : array-like float
            The stress
        load : array-like float
            The load

        Returns
        -------
        strain : array-like float
            The resulting strain
        """
        ...

    @abstractmethod
    def load_secondary_branch(self, load, *, rtol=1e-4, tol=1e-4):
        """Apply the notch-approximation law "backwards", i.e., compute the linear-elastic stress (called "load" or "L" in FKM nonlinear) from the elastic-plastic stress as from the notch approximation.

        This backward step is needed for the pfp FKM nonlinear surface layer & roughness.

        This is to be reimplemented by derived classes

        Parameters
        ----------
        delta_stress : array-like float
            The increment of the elastic-plastic stress as computed by the notch approximation
        rtol : float, optional
            The relative tolerance to which the implicit formulation of the stress gets solved,
            by default 1e-4
        tol : float, optional
            The absolute tolerance to which the implicit formulation of the stress gets solved,
            by default 1e-4

        Returns
        -------
        delta_load : array-like float
            The resulting load or lienar-elastic stress.

        """
        ...

    @abstractmethod
    def stress_secondary_branch(self, load, *, rtol=1e-4, tol=1e-4):
        """Calculate the stress on secondary branches in the stress-strain diagram at a given
        elastic-plastic stress (load), from a FE computation.

        This is to be reimplemented by derived classes

        Parameters
        ----------
        delta_load : array-like float
            The load increment of the hysteresis
        rtol : float, optional
            The relative tolerance to which the implicit formulation of the stress gets solved,
            by default 1e-4
        tol : float, optional
            The absolute tolerance to which the implicit formulation of the stress gets solved,
            by default 1e-4

        Returns
        -------
        delta_stress : array-like float
            The resulting stress increment within the hysteresis
        """
        ...

    @abstractmethod
    def strain_secondary_branch(self, load):
        """Calculate the strain on secondary branches in the stress-strain diagram at a given stress and load.

        This is to be reimplemented by derived classes

        Parameters
        ----------
        delta_sigma : array-like float
            The stress increment
        delta_load : array-like float
            The load increment

        Returns
        -------
        strain : array-like float
            The resulting strain
        """
        ...

    def primary(self, load):
        """Calculate stress and strain for primary branch.

        Parameters
        ----------
        load : array-like
            The load for which the stress and strain are to be calculated

        Returns
        -------
        stress strain : ndarray
            The resulting stress strain data.

            If the argument is scalar, the resulting array is of the strucuture
            ``[<σ>, <ε>]``

            If the argument is an 1D-array with length `n`the resulting array is of the
            structure ``[[<σ1>, <σ2>, <σ3>, ... <σn>], [<ε1>, <ε2>, <ε3>, ... <εn>]]``

        """
        load = np.asarray(load)
        stress = self.stress(load)
        strain = self.strain(stress)
        return np.stack([stress, strain], axis=len(load.shape))

    def secondary(self, delta_load):
        """Calculate stress and strain for secondary branch.

        Parameters
        ----------
        load : array-like
            The load for which the stress and strain are to be calculated

        Returns
        -------
        stress strain : ndarray
            The resulting stress strain data.

            If the argument is scalar, the resulting array is of the strucuture
            ``[<σ>, <ε>]``

            If the argument is an 1D-array with length `n`the resulting array is of the
            structure ``[[<σ1>, <σ2>, <σ3>, ... <σn>], [<ε1>, <ε2>, <ε3>, ... <εn>]]``

        """
        delta_load = np.asarray(delta_load)
        delta_stress = self.stress_secondary_branch(delta_load)
        delta_strain = self.strain_secondary_branch(delta_stress)
        return np.stack([delta_stress, delta_strain], axis=len(delta_load.shape))


[docs] class ExtendedNeuber(NotchApproximationLawBase): r"""Implementation of the extended Neuber notch approximation material relation. This notch approximation law is used for the P_RAM damage parameter in the FKM nonlinear guideline (2019). Given an elastic-plastic stress (and strain) from a linear FE calculation, it derives a corresponding elastic-plastic stress (and strain). Note, the input stress and strain follow a linear relationship :math:`\sigma = E \cdot \epsilon`. The output stress and strain follow the Ramberg-Osgood relation. Parameters ---------- E : float Young's Modulus K : float The strain hardening coefficient, often also designated :math:`K'`, or ``K_prime``. n : float The strain hardening exponent, often also designated :math:`n'`, or ``n_prime``. K_p : float, optional The shape factor (de: Traglastformzahl) Notes ----- The equation implemented is described in the FKM nonlinear reference, chapter 2.5.7. """
[docs] def stress(self, load, *, rtol=1e-4, tol=1e-4): r"""Calculate the stress of the primary path in the stress-strain diagram at a given Parameters ---------- load : array-like float The elastic von Mises stress from a linear elastic FEA. In the FKM nonlinear document, this is also called load "L", because it is derived from a load-time series. Note that this value is scaled to match the actual loading in the assessment, it equals the FEM solution times the transfer factor. rtol : float, optional The relative tolerance to which the implicit formulation of the stress gets solved, by default 1e-4 tol : float, optional The absolute tolerance to which the implicit formulation of the stress gets solved, by default 1e-4 Returns ------- stress : array-like float The resulting elastic-plastic stress according to the notch-approximation law. """ stress = optimize.newton( func=self._stress_implicit, x0=np.asarray(load), fprime=self._d_stress_implicit, args=([load]), rtol=rtol, tol=tol, maxiter=20 ) return stress
[docs] def strain(self, stress): """Calculate the strain of the primary path in the stress-strain diagram at a given stress and load. The formula is given by eq. 2.5-42 of FKM nonlinear. load / stress * self._K_p * e_star Parameters ---------- stress : array-like float The stress load : array-like float The load Returns ------- strain : array-like float The resulting strain """ return self._ramberg_osgood_relation.strain(stress)
[docs] def load(self, stress, *, rtol=1e-4, tol=1e-4): """Apply the notch-approximation law "backwards", i.e., compute the linear-elastic stress (called "load" or "L" in FKM nonlinear) from the elastic-plastic stress as from the notch approximation. This backward step is needed for the pfp FKM nonlinear surface layer & roughness. This method is the inverse operation of "stress", i.e., ``L = load(stress(L))`` and ``S = stress(load(stress))``. Parameters ---------- stress : array-like float The elastic-plastic stress as computed by the notch approximation rtol : float, optional The relative tolerance to which the implicit formulation of the load gets solved, by default 1e-4 tol : float, optional The absolute tolerance to which the implicit formulation of the load gets solved, by default 1e-4 Returns ------- load : array-like float The resulting load or lienar-elastic stress. """ # self._stress_implicit(stress) = 0 # f(sigma) = sigma/E + (sigma/K')^(1/n') - (L/sigma * K_p * e_star) = 0 # => sigma/E + (sigma/K')^(1/n') = (L/sigma * K_p * e_star) # => (sigma/E + (sigma/K')^(1/n')) / K_p * sigma = L * e_star(L) # <=> self._ramberg_osgood_relation.strain(stress) / self._K_p * stress = L * e_star(L) load = optimize.newton( func=self._load_implicit, x0=np.asarray(stress), fprime=self._d_load_implicit, args=([stress]), rtol=rtol, tol=tol, maxiter=20 ) return load
[docs] def stress_secondary_branch(self, delta_load, *, rtol=1e-4, tol=1e-4): """Calculate the stress on secondary branches in the stress-strain diagram at a given elastic-plastic stress (load), from a FE computation. This is done by solving for the root of f(sigma) in eq. 2.5-46 of FKM nonlinear. Parameters ---------- delta_load : array-like float The load increment of the hysteresis rtol : float, optional The relative tolerance to which the implicit formulation of the stress gets solved, by default 1e-4 tol : float, optional The absolute tolerance to which the implicit formulation of the stress gets solved, by default 1e-4 Returns ------- delta_stress : array-like float The resulting stress increment within the hysteresis """ delta_stress = optimize.newton( func=self._stress_secondary_implicit, x0=np.asarray(delta_load), fprime=self._d_stress_secondary_implicit, args=([np.asarray(delta_load, dtype=np.float64)]), rtol=rtol, tol=tol, maxiter=20 ) return delta_stress
[docs] def strain_secondary_branch(self, delta_stress): """Calculate the strain on secondary branches in the stress-strain diagram at a given stress and load. The formula is given by eq. 2.5-46 of FKM nonlinear. Parameters ---------- delta_sigma : array-like float The stress increment delta_load : array-like float The load increment Returns ------- strain : array-like float The resulting strain """ return self._ramberg_osgood_relation.delta_strain(delta_stress)
[docs] def load_secondary_branch(self, delta_stress, *, rtol=1e-4, tol=1e-4): """Apply the notch-approximation law "backwards", i.e., compute the linear-elastic stress (called "load" or "L" in FKM nonlinear) from the elastic-plastic stress as from the notch approximation. This backward step is needed for the pfp FKM nonlinear surface layer & roughness. This method is the inverse operation of "stress", i.e., ``L = load(stress(L))`` and ``S = stress(load(stress))``. Parameters ---------- delta_stress : array-like float The increment of the elastic-plastic stress as computed by the notch approximation rtol : float, optional The relative tolerance to which the implicit formulation of the stress gets solved, by default 1e-4 tol : float, optional The absolute tolerance to which the implicit formulation of the stress gets solved, by default 1e-4 Returns ------- delta_load : array-like float The resulting load or lienar-elastic stress. """ # self._stress_implicit(stress) = 0 # f(sigma) = sigma/E + (sigma/K')^(1/n') - (L/sigma * K_p * e_star) = 0 # => sigma/E + (sigma/K')^(1/n') = (L/sigma * K_p * e_star) # => (sigma/E + (sigma/K')^(1/n')) / K_p * sigma = L * e_star(L) # <=> self._ramberg_osgood_relation.strain(stress) / self._K_p * stress = L * e_star(L) delta_load = optimize.newton( func=self._load_secondary_implicit, x0=np.asarray(delta_stress), fprime=self._d_load_secondary_implicit, args=([delta_stress]), rtol=rtol, tol=tol, maxiter=20 ) return delta_load
def _e_star(self, load): """Compute the plastic corrected strain term e^{\ast} from the Neuber approximation (eq. 2.5-43 in FKM nonlinear) ``e_star = L/K_p / E + (L/K_p / K')^(1/n')`` """ corrected_load = load / self._K_p return self._ramberg_osgood_relation.strain(corrected_load) def _d_e_star(self, load): """Compute the first derivative of self._e_star(load) .. code:: e_star = L/K_p / E + (L/K_p / K')^(1/n') de_star(L)/dL = d/dL[ L/K_p / E + (L/K_p / K')^(1/n') ] = 1/(K_p * E) + tangential_compliance(L/K_p) / K_p """ return 1/(self.K_p * self.E) \ + self._ramberg_osgood_relation.tangential_compliance(load/self.K_p) / self.K_p def _neuber_strain(self, stress, load): """Compute the additional strain term from the Neuber approximation (2nd summand in eq. 2.5-45 in FKM nonlinear) ``(L/sigma * K_p * e_star)`` """ e_star = self._e_star(load) # bad conditioned problem for stress approximately 0 (divide by 0), use factor 1 instead # convert data from int to float if not isinstance(load, float): load = load.astype(float) # factor = load / stress, avoid division by 0 factor = np.divide(load, stress, out=np.ones_like(load), where=stress!=0) return factor * self._K_p * e_star def _stress_implicit(self, stress, load): """Compute the implicit function of the stress, f(sigma), defined in eq.2.5-45 of FKM nonlinear ``f(sigma) = sigma/E + (sigma/K')^(1/n') - (L/sigma * K_p * e_star)`` """ return self._ramberg_osgood_relation.strain(stress) - self._neuber_strain(stress, load) def _d_stress_implicit(self, stress, load): """Compute the first derivative of self._stress_implicit ``df/dsigma`` """ e_star = self._e_star(load) return self._ramberg_osgood_relation.tangential_compliance(stress) \ - load * self._K_p * e_star \ * -np.power(stress, -2, out=np.ones_like(stress), where=stress!=0) def _delta_e_star(self, delta_load): """Compute the plastic corrected strain term e^{\ast} from the Neuber approximation (eq. 2.5-43 in FKM nonlinear), for secondary branches in the stress-strain diagram """ corrected_load = delta_load / self._K_p return self._ramberg_osgood_relation.delta_strain(corrected_load) def _d_delta_e_star(self, delta_load): """Compute the first derivative of self._delta_e_star(load) .. code:: delta_e_star = ΔL/K_p / E + 2*(ΔL/K_p / (2*K'))^(1/n') = ΔL/K_p / E + 2*(ΔL/(2*K_p) / K')^(1/n') d_delta_e_star(ΔL)/dΔL = d/dΔL[ ΔL/K_p / E + 2*(ΔL/(2*K_p) / K')^(1/n') ] = 1/(K_p * E) + 2*tangential_compliance(ΔL/(2*K_p)) / (2*K_p) = 1/(K_p * E) + tangential_compliance(ΔL/(2*K_p)) / K_p """ return 1/(self.K_p * self.E) \ + self._ramberg_osgood_relation.tangential_compliance(delta_load/(2*self.K_p)) / self.K_p def _neuber_strain_secondary(self, delta_stress, delta_load): """Compute the additional strain term from the Neuber approximation (2nd summand in eq. 2.5-45 in FKM nonlinear)""" delta_e_star = self._delta_e_star(delta_load) # bad conditioned problem for delta_stress approximately 0 (divide by 0), use factor 1 instead # convert data from int to float if not isinstance(delta_load, float): delta_load = delta_load.astype(float) # factor = load / stress, avoid division by 0 factor = np.divide(delta_load, delta_stress, out=np.ones_like(delta_load), where=delta_stress!=0) return factor * self._K_p * delta_e_star def _stress_secondary_implicit(self, delta_stress, delta_load): """Compute the implicit function of the stress, f(sigma), defined in eq.2.5-46 of FKM nonlinear""" return self._ramberg_osgood_relation.delta_strain(delta_stress) - self._neuber_strain_secondary(delta_stress, delta_load) def _d_stress_secondary_implicit(self, delta_stress, delta_load): """Compute the first derivative of self._stress_secondary_implicit Note, the derivative of `self._ramberg_osgood_relation.delta_strain` is: .. code:: d/dΔsigma delta_strain(Δsigma) = d/dΔsigma 2*strain(Δsigma/2) = 2*d/dΔsigma strain(Δsigma/2) = 2 * 1/2 * tangential_compliance(Δsigma/2) = self._ramberg_osgood_relation.tangential_compliance(delta_stress/2) """ delta_e_star = self._delta_e_star(delta_load) return self._ramberg_osgood_relation.tangential_compliance(delta_stress/2) \ - delta_load * self._K_p * delta_e_star \ * -np.power(delta_stress, -2, out=np.ones_like(delta_stress), where=delta_stress!=0) def _load_implicit(self, load, stress): """Compute the implicit function of the stress, f(sigma), as a function of the load, defined in eq.2.5-45 of FKM nonlinear. This is needed to apply the notch approximation law "backwards", i.e., to get from stress back to load. This is required for the FKM nonlinear roughness & surface layer. ``f(L) = sigma/E + (sigma/K')^(1/n') - (L/sigma * K_p * e_star(L))`` """ return self._stress_implicit(stress, load) def _d_load_implicit(self, load, stress): """Compute the first derivative of self._load_implicit .. code:: f(L) = sigma/E + (sigma/K')^(1/n') - (L/sigma * K_p * e_star(L)) df/dL = d/dL [ -(L/sigma * K_p * e_star(L))] = -1/sigma * K_p * e_star(L) - L/sigma * K_p * de_star/dL """ return -1/stress * self.K_p * self._e_star(load) \ - load/stress * self.K_p * self._d_e_star(load) def _load_secondary_implicit(self, delta_load, delta_stress): """Compute the implicit function of the stress, f(Δsigma), as a function of the load, defined in eq.2.5-46 of FKM nonlinear. This is needed to apply the notch approximation law "backwards", i.e., to get from stress back to load. This is required for the FKM nonlinear roughness & surface layer. ``f(ΔL) = Δsigma/E + 2*(Δsigma/(2*K'))^(1/n') - (ΔL/Δsigma * K_p * Δe_star(ΔL))`` """ return self._stress_secondary_implicit(delta_stress, delta_load) def _d_load_secondary_implicit(self, delta_load, delta_stress): """Compute the first derivative of self._load_secondary_implicit .. code:: f(ΔL) = Δsigma/E + 2*(Δsigma/(2*K'))^(1/n') - (ΔL/Δsigma * K_p * Δe_star(ΔL)) df/dΔL = d/dΔL [ -(ΔL/Δsigma * K_p * Δe_star(ΔL))] = -1/Δsigma * K_p * Δe_star(ΔL) - ΔL/Δsigma * K_p * dΔe_star/dΔL """ return -1/delta_stress * self.K_p * self._delta_e_star(delta_load) \ - delta_load/delta_stress * self.K_p * self._d_delta_e_star(delta_load)
[docs] class NotchApproxBinner: """Binning for notch approximation laws, as described in FKM nonlinear 2.5.8.2, p.55. The implicitly defined stress function of the notch approximation law is precomputed for various loads at a fixed number of equispaced `bins`. The values are stored in two look-up tables for the primary and secondary branches of the stress-strain hysteresis curves. When stress and strain values are needed for a given load, the nearest value of the corresponding bin is retrived. This is faster than invoking the nonlinear root finding algorithm for every new load. There are two variants of the data structure. * First, for a single assessment point, the lookup-table contains one load, strain and stress value in every bin. * Second, for vectorized assessment of multiple nodes at once, the lookup-table contains at every load bin an array with stress and strain values for every node. The representative load, stored in the lookup table and used for the lookup is the first element of the given load array. Parameters ---------- notch_approximation_law : NotchApproximationLawBase The law for the notch approximation to be used. number_of_bins : int, optional The number of bins in the lookup table, default 100 """ def __init__(self, notch_approximation_law, number_of_bins=100): self._n_bins = number_of_bins self._notch_approximation_law = notch_approximation_law self._ramberg_osgood_relation = notch_approximation_law.ramberg_osgood_relation self._max_load_rep = None self._max_load_index = None
[docs] def initialize(self, max_load): """Initialize with a maximum expected load. Parameters ---------- max_load : array_like The state of the maximum nominal load that is expected. The first element is chosen as representative to calculate the lookup table. Returns ------- self """ max_load = np.asarray(max_load) self._max_load_rep, _ = self._representative_value_and_sign(max_load) load = self._param_for_lut(self._n_bins, max_load) self._lut_primary = self._notch_approximation_law.primary(load) delta_load = self._param_for_lut(2 * self._n_bins, 2.0*max_load) self._lut_secondary = self._notch_approximation_law.secondary(delta_load) return self
@property def ramberg_osgood_relation(self): """the Ramberg-Osgood relation object, i.e., an object of type RambergOsgood provided by the notch approximation law. """ return self._ramberg_osgood_relation
[docs] def primary(self, load): """Lookup the stress strain of the primary branch. Parameters ---------- load : array-like The load as argument for the stress strain laws. If non-scalar, the first element will be used to look it up in the lookup table. Returns ------- stress strain : ndarray The resulting stress strain data. If the argument is scalar, the resulting array is of the strucuture ``[<σ>, <ε>]`` If the argument is an 1D-array with length `n`the resulting array is of the structure ``[[<σ1>, <σ2>, <σ3>, ... <σn>], [<ε1>, <ε2>, <ε3>, ... <εn>]]`` """ self._raise_if_uninitialized() load_rep, sign = self._representative_value_and_sign(load) if load_rep > self._max_load_rep: msg = f"Requested load `{load_rep}`, higher than initialized maximum load `{self._max_load_rep}`" raise ValueError(msg) idx = int(np.ceil(load_rep / self._max_load_rep * self._n_bins)) - 1 return sign * self._lut_primary[idx, :]
[docs] def secondary(self, delta_load): """Lookup the stress strain of the secondary branch. Parameters ---------- load : array-like The load as argument for the stress strain laws. If non-scalar, the first element will be used to look it up in the lookup table. Returns ------- stress strain : ndarray The resulting stress strain data. If the argument is scalar, the resulting array is of the strucuture ``[<σ>, <ε>]`` If the argument is an 1D-array with length `n`the resulting array is of the structure ``[[<σ1>, <σ2>, <σ3>, ..., <σn>], [<ε1>, <ε2>, <ε3>, ..., <εn>]]`` """ self._raise_if_uninitialized() delta_load_rep, sign = self._representative_value_and_sign(delta_load) if delta_load_rep > 2.0 * self._max_load_rep: msg = f"Requested load `{delta_load_rep}`, higher than initialized maximum delta load `{2.0*self._max_load_rep}`" raise ValueError(msg) idx = int(np.ceil(delta_load_rep / (2.0*self._max_load_rep) * 2*self._n_bins)) - 1 return sign * self._lut_secondary[idx, :]
def _raise_if_uninitialized(self): if self._max_load_rep is None: raise RuntimeError("NotchApproxBinner not initialized.") def _param_for_lut(self, number_of_bins, max_val): scale = np.linspace(0.0, 1.0, number_of_bins + 1)[1:] max_val, scale_m = np.meshgrid(max_val, scale) return (max_val * scale_m) def _representative_value_and_sign(self, value): value = np.asarray(value) single_point = len(value.shape) == 0 if self._max_load_index is None and single_point: self._max_load_index = 0 value_rep = value if single_point else self._first_or_maximum_load_of_mesh(value) return np.abs(value_rep), np.sign(value_rep) def _first_or_maximum_load_of_mesh(self, mesh_values): if self._max_load_index is None: if mesh_values[0] != 0.0: self._max_load_index = 0 else: self._max_load_index = np.argmax(np.abs(mesh_values)) if mesh_values[self._max_load_index] == 0.0: raise ValueError( "NotchApproxBinner must have at least one non zero point in max_load." ) return mesh_values[self._max_load_index]