"""Bearing Element module.

This module defines the BearingElement classes which will be used to represent the rotor
bearings and seals. There're 6 different classes to represent bearings options,
and 2 element options with 8 or 12 degrees of freedom.
"""
# fmt: off
import os
import warnings
from pathlib import Path

import numpy as np
import plotly.graph_objects as go
import scipy.interpolate as interpolate
import toml

from ross.element import Element
from ross.fluid_flow import fluid_flow as flow
from ross.fluid_flow.fluid_flow_coefficients import (
    calculate_short_damping_matrix, calculate_short_stiffness_matrix)
from ross.units import check_units
from ross.utils import read_table_file

# fmt: on

__all__ = [
    "BearingElement",
    "SealElement",
    "BallBearingElement",
    "RollerBearingElement",
    "BearingElement6DoF",
    "MagneticBearingElement",
]


class _Coefficient:
    """Auxiliary bearing coefficient class.

    This class takes bearing elements' coefficients and frequencies values and
    interpolate the arrays when necessary.

    Parameters
    ----------
    coefficient : int, float, array, pint.Quantity
        Bearing element stiffness or damping coefficient (direct or cross-coupled).
        If coefficient is int or float, it is considered constant along the frequency
        array. If coefficient is an array, it's interpolated with the frequency array.
    frequency: array, pint.Quantity, optional
        Array with the frequencies (rad/s).
        Frequency is optional only if coefficient is an int or a float (constant value).

    Returns
    -------
    The bearing element dynamic coefficient.
        Kxx, Kxy, Kyx, Kyy, Cxx, Cxy, Cyx, Cyy.

    Examples
    --------
    >>> bearing = bearing_example()
    >>> bearing.kxx # doctest: +ELLIPSIS
    [1000000.0...
    """

    def __init__(self, coefficient, frequency=None):
        if isinstance(coefficient, (int, float)):
            if frequency is not None and type(frequency) != float:
                coefficient = [coefficient for _ in range(len(frequency))]
            else:
                coefficient = [coefficient]

        self.coefficient = coefficient
        self.frequency = frequency

        if len(self.coefficient) > 1:
            try:
                with warnings.catch_warnings():
                    warnings.simplefilter("ignore")
                    self.interpolated = interpolate.UnivariateSpline(
                        self.frequency, self.coefficient
                    )
            #  dfitpack.error is not exposed by scipy
            #  so a bare except is used
            except:
                try:
                    if len(self.frequency) in (2, 3):
                        self.interpolated = interpolate.interp1d(
                            self.frequency,
                            self.coefficient,
                            kind=len(self.frequency) - 1,
                            fill_value="extrapolate",
                        )
                except:
                    raise ValueError(
                        "Arguments (coefficients and frequency)"
                        " must have the same dimension"
                    )
        else:
            self.interpolated = lambda x: np.array(self.coefficient[0])

    def __eq__(self, other):
        """Equality method for comparasions.

        Parameters
        ----------
        other: object
            The second object to be compared with.

        Returns
        -------
        bool
            True if the comparison is true; False otherwise.

        Examples
        --------
        >>> bearing1 = bearing_example()
        >>> bearing2 = bearing_example()
        >>> bearing1.kxx == bearing2.kxx
        True
        """
        if np.allclose(self.__dict__["coefficient"], other.__dict__["coefficient"]):
            return True
        else:
            return False

    def __repr__(self):
        """Return a string representation of a bearing element.

        Returns
        -------
        A string representation of a bearing element object.

        Examples
        --------
        >>> bearing = bearing_example()
        >>> bearing.cxx # doctest: +ELLIPSIS
        [200.0, 200.0, 200.0,...
        """
        return repr(self.coefficient)

    def __getitem__(self, item):
        """Return an element from the coeffcient array.

        This method allows the elements from the coefficient array to be returned as
        ints or floats, given an index (item).

        Parameters
        ----------
        item : int, slices
            Array index.

        Returns
        -------
        An element from the coefficient array.

        Examples
        --------
        >>> bearing = bearing_example()
        >>> bearing.kxx[0]
        1000000.0
        """
        return self.coefficient[item]

    def plot(self, **kwargs):
        """Plot coefficient vs frequency.

        Parameters
        ----------
        **kwargs : optional
            Additional key word arguments can be passed to change the plot layout only
            (e.g. width=1000, height=800, ...).
            *See Plotly Python Figure Reference for more information.

        Returns
        -------
        fig : Plotly graph_objects.Figure()
            The figure object with the plot.

        Example
        -------
        >>> bearing = bearing_example()
        >>> fig = bearing.kxx.plot()
        >>> # fig.show()
        """
        frequency_range = np.linspace(min(self.frequency), max(self.frequency), 30)

        fig = go.Figure()

        fig.add_trace(
            go.Scatter(
                x=frequency_range,
                y=self.interpolated(frequency_range),
                mode="lines",
                line=dict(width=3.0, color="royalblue"),
                showlegend=False,
                hovertemplate=("Frequency: %{x:.2f}<br>" + "Coefficient: %{y:.3e}"),
            )
        )

        fig.update_xaxes(
            title_text="<b>Frequency (rad/s)</b>",
            title_font=dict(family="Arial", size=20),
            tickfont=dict(size=16),
            gridcolor="lightgray",
            showline=True,
            linewidth=2.5,
            linecolor="black",
            mirror=True,
        )
        fig.update_yaxes(
            title_font=dict(family="Arial", size=20),
            tickfont=dict(size=16),
            gridcolor="lightgray",
            showline=True,
            linewidth=2.5,
            linecolor="black",
            mirror=True,
            exponentformat="power",
        )
        fig.update_layout(
            width=800,
            height=600,
            plot_bgcolor="white",
            hoverlabel_align="right",
            **kwargs,
        )

        return fig


class _Stiffness_Coefficient(_Coefficient):
    """Stiffness coefficient auxiliary class.

    Inherits from _Coefficient class. It will adapt the plot layout to stiffness
    coefficients.
    """

    def plot(self, **kwargs):
        """Plot stiffness coefficient vs frequency.

        Parameters
        ----------
        **kwargs : optional
            Additional key word arguments can be passed to change the plot layout only
            (e.g. width=1000, height=800, ...).
            *See Plotly Python Figure Reference for more information.

        Returns
        -------
        fig : Plotly graph_objects.Figure()
            The figure object with the plot.

        Example
        -------
        >>> bearing = bearing_example()
        >>> fig = bearing.kxx.plot()
        >>> # fig.show()
        """
        fig = super().plot(**kwargs)
        fig.update_yaxes(title_text="<b>Stiffness (N/m)</b>")

        return fig


class _Damping_Coefficient(_Coefficient):
    """Stiffness coefficient auxiliary class.

    Inherits from _Coefficient class. It will adapt the plot layout to damping
    coefficients.
    """

    def plot(self, **kwargs):
        """Plot damping coefficient vs frequency.

        Parameters
        ----------
        **kwargs : optional
            Additional key word arguments can be passed to change the plot layout only
            (e.g. width=1000, height=800, ...).
            *See Plotly Python Figure Reference for more information.

        Returns
        -------
        fig : Plotly graph_objects.Figure()
            The figure object with the plot.

        Example
        -------
        >>> bearing = bearing_example()
        >>> fig = bearing.cxx.plot()
        >>> # fig.show()
        """
        fig = super().plot(**kwargs)
        fig.update_yaxes(title_text="<b>Damping (Ns/m)</b>")

        return fig


class BearingElement(Element):
    """A bearing element.

    This class will create a bearing element.
    Parameters can be a constant value or speed dependent.
    For speed dependent parameters, each argument should be passed
    as an array and the correspondent speed values should also be
    passed as an array.
    Values for each parameter will be interpolated for the speed.

    Parameters
    ----------
    n: int
        Node which the bearing will be located in
    kxx: float, array, pint.Quantity
        Direct stiffness in the x direction.
    cxx: float, array, pint.Quantity
        Direct damping in the x direction.
    kyy: float, array, pint.Quantity, optional
        Direct stiffness in the y direction.
        (defaults to kxx)
    cyy: float, array, pint.Quantity, optional
        Direct damping in the y direction.
        (defaults to cxx)
    kxy: float, array, pint.Quantity ,optional
        Cross coupled stiffness in the x direction.
        (defaults to 0)
    cxy: float, array, pint.Quantity, optional
        Cross coupled damping in the x direction.
        (defaults to 0)
    kyx: float, array, pint.Quantity, optional
        Cross coupled stiffness in the y direction.
        (defaults to 0)
    cyx: float, array, pint.Quantity, optional
        Cross coupled damping in the y direction.
        (defaults to 0)
    frequency: array, pint.Quantity, optional
        Array with the frequencies (rad/s).
    tag: str, optional
        A tag to name the element
        Default is None.
    n_link: int, optional
        Node to which the bearing will connect. If None the bearing is
        connected to ground.
        Default is None.
    scale_factor: float, optional
        The scale factor is used to scale the bearing drawing.
        Default is 1.
    color : str, optional
        A color to be used when the element is represented.
        Default is '#355d7a' (Cardinal).

    Examples
    --------
    >>> # A bearing element located in the first rotor node, with these
    >>> # following stiffness and damping coefficients and speed range from
    >>> # 0 to 200 rad/s
    >>> import ross as rs
    >>> kxx = 1e6
    >>> kyy = 0.8e6
    >>> cxx = 2e2
    >>> cyy = 1.5e2
    >>> frequency = np.linspace(0, 200, 11)
    >>> bearing0 = rs.BearingElement(n=0, kxx=kxx, kyy=kyy, cxx=cxx, cyy=cyy, frequency=frequency)
    >>> bearing0.K(frequency) # doctest: +ELLIPSIS
    array([[[1000000., 1000000., ...
    >>> bearing0.C(frequency) # doctest: +ELLIPSIS
    array([[[200., 200., ...
    """

    @check_units
    def __init__(
        self,
        n,
        kxx,
        cxx,
        kyy=None,
        kxy=0,
        kyx=0,
        cyy=None,
        cxy=0,
        cyx=0,
        frequency=None,
        tag=None,
        n_link=None,
        scale_factor=1,
        color="#355d7a",
    ):

        args = ["kxx", "kyy", "kxy", "kyx", "cxx", "cyy", "cxy", "cyx"]

        # all args to coefficients
        args_dict = locals()
        coefficients = {}

        if kyy is None:
            args_dict["kyy"] = kxx
        if cyy is None:
            args_dict["cyy"] = cxx

        for arg in args:
            if arg[0] == "k":
                coefficients[arg] = _Stiffness_Coefficient(
                    coefficient=args_dict[arg], frequency=args_dict["frequency"]
                )
            else:
                coefficients[arg] = _Damping_Coefficient(
                    args_dict[arg], args_dict["frequency"]
                )

        coefficients_len = [len(v.coefficient) for v in coefficients.values()]

        if frequency is not None and type(frequency) != float:
            coefficients_len.append(len(args_dict["frequency"]))
            if len(set(coefficients_len)) > 1:
                raise ValueError(
                    "Arguments (coefficients and frequency)"
                    " must have the same dimension"
                )
        else:
            for c in coefficients_len:
                if c != 1:
                    raise ValueError(
                        "Arguments (coefficients and frequency)"
                        " must have the same dimension"
                    )

        for k, v in coefficients.items():
            setattr(self, k, v)

        self.n = n
        self.n_link = n_link
        self.n_l = n
        self.n_r = n
        if frequency is not None:
            self.frequency = np.array(frequency, dtype=np.float64)
        else:
            self.frequency = frequency
        self.tag = tag
        self.color = color
        self.scale_factor = scale_factor
        self.dof_global_index = None

    def __repr__(self):
        """Return a string representation of a bearing element.

        Returns
        -------
        A string representation of a bearing element object.

        Examples
        --------
        >>> bearing = bearing_example()
        >>> bearing # doctest: +ELLIPSIS
        BearingElement(n=0, n_link=None,
         kxx=[...
        """
        return (
            f"{self.__class__.__name__}"
            f"(n={self.n}, n_link={self.n_link},\n"
            f" kxx={self.kxx}, kxy={self.kxy},\n"
            f" kyx={self.kyx}, kyy={self.kyy},\n"
            f" cxx={self.cxx}, cxy={self.cxy},\n"
            f" cyx={self.cyx}, cyy={self.cyy},\n"
            f" frequency={self.frequency}, tag={self.tag!r})"
        )

    def __eq__(self, other):
        """Equality method for comparasions.

        Parameters
        ----------
        other: object
            The second object to be compared with.

        Returns
        -------
        bool
            True if the comparison is true; False otherwise.

        Examples
        --------
        >>> bearing1 = bearing_example()
        >>> bearing2 = bearing_example()
        >>> bearing1 == bearing2
        True
        """
        compared_attributes = [
            "kxx",
            "kyy",
            "kxy",
            "kyx",
            "cxx",
            "cyy",
            "cxy",
            "cyx",
            "frequency",
            "n",
            "n_link",
        ]
        if isinstance(other, self.__class__):
            return all(
                (
                    np.array(getattr(self, attr)).all()
                    == np.array(getattr(other, attr)).all()
                    for attr in compared_attributes
                )
            )
        return False

    def __hash__(self):
        return hash(self.tag)

    def save(self, file):
        try:
            data = toml.load(file)
        except FileNotFoundError:
            data = {}

        args = {
            "n": self.n,
            "kxx": [float(i) for i in self.kxx.coefficient],
            "cxx": [float(i) for i in self.cxx.coefficient],
            "kyy": [float(i) for i in self.kyy.coefficient],
            "kxy": [float(i) for i in self.kxy.coefficient],
            "kyx": [float(i) for i in self.kyx.coefficient],
            "cyy": [float(i) for i in self.cyy.coefficient],
            "cxy": [float(i) for i in self.cxy.coefficient],
            "cyx": [float(i) for i in self.cyx.coefficient],
            "tag": self.tag,
            "n_link": self.n_link,
            "scale_factor": self.scale_factor,
        }
        if self.frequency is not None:
            args["frequency"] = [float(i) for i in self.frequency]
        else:
            args["frequency"] = self.frequency

        data[f"{self.__class__.__name__}_{self.tag}"] = args

        with open(file, "w") as f:
            toml.dump(data, f)

    def dof_mapping(self):
        """Degrees of freedom mapping.

        Returns a dictionary with a mapping between degree of freedom and its
        index.

        Returns
        -------
        dof_mapping : dict
            A dictionary containing the degrees of freedom and their indexes.

        Examples
        --------
        The numbering of the degrees of freedom for each node.

        Being the following their ordering for a node:

        x_0 - horizontal translation
        y_0 - vertical translation

        >>> bearing = bearing_example()
        >>> bearing.dof_mapping()
        {'x_0': 0, 'y_0': 1}
        """
        return dict(x_0=0, y_0=1)

    def M(self):
        """Mass matrix for an instance of a bearing element.

        This method returns the mass matrix for an instance of a bearing
        element.

        Returns
        -------
        M : np.ndarray
            Mass matrix.

        Examples
        --------
        >>> bearing = bearing_example()
        >>> bearing.M()
        array([[0., 0.],
               [0., 0.]])
        """
        M = np.zeros_like(self.K(0))

        return M

    def K(self, frequency):
        """Stiffness matrix for an instance of a bearing element.

        This method returns the stiffness matrix for an instance of a bearing
        element.

        Parameters
        ----------
        frequency : float
            The excitation frequency.

        Returns
        -------
        K : np.ndarray
            A 2x2 matrix of floats containing the kxx, kxy, kyx, and kyy values.

        Examples
        --------
        >>> bearing = bearing_example()
        >>> bearing.K(0)
        array([[1000000.,       0.],
               [      0.,  800000.]])
        """
        kxx = self.kxx.interpolated(frequency)
        kyy = self.kyy.interpolated(frequency)
        kxy = self.kxy.interpolated(frequency)
        kyx = self.kyx.interpolated(frequency)

        K = np.array([[kxx, kxy], [kyx, kyy]])

        if self.n_link is not None:
            # fmt: off
            K = np.vstack((np.hstack([K, -K]),
                           np.hstack([-K, K])))
            # fmt: on

        return K

    def C(self, frequency):
        """Damping matrix for an instance of a bearing element.

        This method returns the damping matrix for an instance of a bearing
        element.

        Parameters
        ----------
        frequency : float
            The excitation frequency.

        Returns
        -------
        C : np.ndarray
            A 2x2 matrix of floats containing the cxx, cxy, cyx, and cyy values.

        Examples
        --------
        >>> bearing = bearing_example()
        >>> bearing.C(0)
        array([[200.,   0.],
               [  0., 150.]])
        """
        cxx = self.cxx.interpolated(frequency)
        cyy = self.cyy.interpolated(frequency)
        cxy = self.cxy.interpolated(frequency)
        cyx = self.cyx.interpolated(frequency)

        C = np.array([[cxx, cxy], [cyx, cyy]])

        if self.n_link is not None:
            # fmt: off
            C = np.vstack((np.hstack([C, -C]),
                           np.hstack([-C, C])))
            # fmt: on

        return C

    def G(self):
        """Gyroscopic matrix for an instance of a bearing element.

        This method returns the mass matrix for an instance of a bearing
        element.

        Returns
        -------
        G : np.ndarray
            A 2x2 matrix of floats.

        Examples
        --------
        >>> bearing = bearing_example()
        >>> bearing.G()
        array([[0., 0.],
               [0., 0.]])
        """
        G = np.zeros_like(self.K(0))

        return G

    def _patch(self, position, fig):
        """Bearing element patch.

        Patch that will be used to draw the bearing element using Plotly library.

        Parameters
        ----------
        position : tuple
            Position (z, y_low, y_upp) in which the patch will be drawn.
        fig : plotly.graph_objects.Figure
            The figure object which traces are added on.

        Returns
        -------
        fig : plotly.graph_objects.Figure
            The figure object which traces are added on.
        """
        default_values = dict(
            mode="lines",
            line=dict(width=3.5, color=self.color),
            name=self.tag,
            legendgroup="bearings",
            showlegend=False,
            hoverinfo="none",
        )

        # geometric factors
        zpos, ypos, ypos_s = position

        icon_h = ypos_s - ypos  # bearing icon height
        icon_w = icon_h / 2.0  # bearing icon width
        coils = 6  # number of points to generate spring
        n = 5  # number of ground lines
        step = icon_w / (coils + 1)  # spring step

        zs0 = zpos - (icon_w / 2.0)
        zs1 = zpos + (icon_w / 2.0)
        ys0 = ypos + 0.25 * icon_h

        # plot bottom base
        x_bot = [zpos, zpos, zs0, zs1]
        yl_bot = [ypos, ys0, ys0, ys0]
        yu_bot = [-y for y in yl_bot]

        fig.add_trace(go.Scatter(x=x_bot, y=yl_bot, **default_values))
        fig.add_trace(go.Scatter(x=x_bot, y=yu_bot, **default_values))

        # plot top base
        x_top = [zpos, zpos, zs0, zs1]
        yl_top = [
            ypos + icon_h,
            ypos + 0.75 * icon_h,
            ypos + 0.75 * icon_h,
            ypos + 0.75 * icon_h,
        ]
        yu_top = [-y for y in yl_top]
        fig.add_trace(go.Scatter(x=x_top, y=yl_top, **default_values))
        fig.add_trace(go.Scatter(x=x_top, y=yu_top, **default_values))

        # plot ground
        if self.n_link is None:
            zl_g = [zs0 - step, zs1 + step]
            yl_g = [yl_top[0], yl_top[0]]
            yu_g = [-y for y in yl_g]
            fig.add_trace(go.Scatter(x=zl_g, y=yl_g, **default_values))
            fig.add_trace(go.Scatter(x=zl_g, y=yu_g, **default_values))

            step2 = (zl_g[1] - zl_g[0]) / n
            for i in range(n + 1):
                zl_g2 = [(zs0 - step) + step2 * (i), (zs0 - step) + step2 * (i + 1)]
                yl_g2 = [yl_g[0], 1.1 * yl_g[0]]
                yu_g2 = [-y for y in yl_g2]
                fig.add_trace(go.Scatter(x=zl_g2, y=yl_g2, **default_values))
                fig.add_trace(go.Scatter(x=zl_g2, y=yu_g2, **default_values))

        # plot spring
        z_spring = np.array([zs0, zs0, zs0, zs0])
        yl_spring = np.array([ys0, ys0 + step, ys0 + icon_w - step, ys0 + icon_w])

        for i in range(coils):
            z_spring = np.insert(z_spring, i + 2, zs0 - (-1) ** i * step)
            yl_spring = np.insert(yl_spring, i + 2, ys0 + (i + 1) * step)
        yu_spring = [-y for y in yl_spring]

        fig.add_trace(go.Scatter(x=z_spring, y=yl_spring, **default_values))
        fig.add_trace(go.Scatter(x=z_spring, y=yu_spring, **default_values))

        # plot damper - base
        z_damper1 = [zs1, zs1]
        yl_damper1 = [ys0, ys0 + 2 * step]
        yu_damper1 = [-y for y in yl_damper1]
        fig.add_trace(go.Scatter(x=z_damper1, y=yl_damper1, **default_values))
        fig.add_trace(go.Scatter(x=z_damper1, y=yu_damper1, **default_values))

        # plot damper - center
        z_damper2 = [zs1 - 2 * step, zs1 - 2 * step, zs1 + 2 * step, zs1 + 2 * step]
        yl_damper2 = [ys0 + 5 * step, ys0 + 2 * step, ys0 + 2 * step, ys0 + 5 * step]
        yu_damper2 = [-y for y in yl_damper2]
        fig.add_trace(go.Scatter(x=z_damper2, y=yl_damper2, **default_values))
        fig.add_trace(go.Scatter(x=z_damper2, y=yu_damper2, **default_values))

        # plot damper - top
        z_damper3 = [z_damper2[0], z_damper2[2], zs1, zs1]
        yl_damper3 = [
            ys0 + 4 * step,
            ys0 + 4 * step,
            ys0 + 4 * step,
            ypos + 1.5 * icon_w,
        ]
        yu_damper3 = [-y for y in yl_damper3]

        fig.add_trace(go.Scatter(x=z_damper3, y=yl_damper3, **default_values))
        fig.add_trace(go.Scatter(x=z_damper3, y=yu_damper3, **default_values))

        return fig

    @classmethod
    def table_to_toml(cls, n, file):
        """Convert bearing parameters to toml.

        Convert a table with parameters of a bearing element to a dictionary ready to
        save to a toml file that can be later loaded by ross.

        Parameters
        ----------
        n : int
            The node in which the bearing will be located in the rotor.
        file: str
            Path to the file containing the bearing parameters.

        Returns
        -------
        data: dict
            A dict that is ready to save to toml and readable by ross.

        Examples
        --------
        >>> import os
        >>> file_path = os.path.dirname(os.path.realpath(__file__)) + '/tests/data/bearing_seal_si.xls'
        >>> BearingElement.table_to_toml(0, file_path) # doctest: +ELLIPSIS
        {'n': 0, 'kxx': array([...
        """
        b_elem = cls.from_table(n, file)
        data = {
            "n": b_elem.n,
            "kxx": b_elem.kxx.coefficient,
            "cxx": b_elem.cxx.coefficient,
            "kyy": b_elem.kyy.coefficient,
            "kxy": b_elem.kxy.coefficient,
            "kyx": b_elem.kyx.coefficient,
            "cyy": b_elem.cyy.coefficient,
            "cxy": b_elem.cxy.coefficient,
            "cyx": b_elem.cyx.coefficient,
            "frequency": b_elem.frequency,
        }
        return data

    @classmethod
    def from_table(
        cls,
        n,
        file,
        sheet_name=0,
        tag=None,
        n_link=None,
        scale_factor=1,
        color="#355d7a",
    ):
        """Instantiate a bearing using inputs from an Excel table.

        A header with the names of the columns is required. These names should match the
        names expected by the routine (usually the names of the parameters, but also
        similar ones). The program will read every row bellow the header
        until they end or it reaches a NaN.

        Parameters
        ----------
        n : int
            The node in which the bearing will be located in the rotor.
        file: str
            Path to the file containing the bearing parameters.
        sheet_name: int or str, optional
            Position of the sheet in the file (starting from 0) or its name. If none is
            passed, it is assumed to be the first sheet in the file.
        tag : str, optional
            A tag to name the element.
            Default is None.
        n_link : int, optional
            Node to which the bearing will connect. If None the bearing is connected to
            ground.
            Default is None.
        scale_factor : float, optional
            The scale factor is used to scale the bearing drawing.
            Default is 1.
        color : str, optional
            A color to be used when the element is represented.
            Default is '#355d7a' (Cardinal).

        Returns
        -------
        bearing: rs.BearingElement
            A bearing object.

        Examples
        --------
        >>> import os
        >>> file_path = os.path.dirname(os.path.realpath(__file__)) + '/tests/data/bearing_seal_si.xls'
        >>> BearingElement.from_table(0, file_path, n_link=1) # doctest: +ELLIPSIS
        BearingElement(n=0, n_link=1,
         kxx=array([...
        """
        parameters = read_table_file(file, "bearing", sheet_name, n)
        return cls(
            n=parameters["n"],
            kxx=parameters["kxx"],
            cxx=parameters["cxx"],
            kyy=parameters["kyy"],
            kxy=parameters["kxy"],
            kyx=parameters["kyx"],
            cyy=parameters["cyy"],
            cxy=parameters["cxy"],
            cyx=parameters["cyx"],
            frequency=parameters["frequency"],
            tag=tag,
            n_link=n_link,
            scale_factor=scale_factor,
            color=color,
        )

    @classmethod
    def from_fluid_flow(
        cls,
        n,
        nz,
        ntheta,
        nradius,
        length,
        omega,
        p_in,
        p_out,
        radius_rotor,
        radius_stator,
        visc,
        rho,
        eccentricity=None,
        load=None,
    ):
        """Instantiate a bearing using inputs from its fluid flow.

        Parameters
        ----------
        n : int
            The node in which the bearing will be located in the rotor.

        Grid related
        ^^^^^^^^^^^^
        Describes the discretization of the problem
        nz: int
            Number of points along the Z direction (direction of flow).
        ntheta: int
            Number of points along the direction theta. NOTE: ntheta must be odd.
        nradius: int
            Number of points along the direction r.
        length: float
            Length in the Z direction (m).

        Operation conditions
        ^^^^^^^^^^^^^^^^^^^^
        Describes the operation conditions.
        omega: float
            Rotation of the rotor (rad/s).
        p_in: float
            Input Pressure (Pa).
        p_out: float
            Output Pressure (Pa).
        load: float
            Load applied to the rotor (N).

        Geometric data of the problem
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        Describes the geometric data of the problem.
        radius_rotor: float
            Rotor radius (m).
        radius_stator: float
            Stator Radius (m).
        eccentricity: float
            Eccentricity (m) is the euclidean distance between rotor and stator centers.
            The center of the stator is in position (0,0).

        Fluid characteristics
        ^^^^^^^^^^^^^^^^^^^^^
        Describes the fluid characteristics.
        visc: float
            Viscosity (Pa.s).
        rho: float
            Fluid density(Kg/m^3).

        Returns
        -------
        bearing: rs.BearingElement
            A bearing object.

        Examples
        --------
        >>> nz = 30
        >>> ntheta = 20
        >>> nradius = 11
        >>> length = 0.03
        >>> omega = 157.1
        >>> p_in = 0.
        >>> p_out = 0.
        >>> radius_rotor = 0.0499
        >>> radius_stator = 0.05
        >>> eccentricity = (radius_stator - radius_rotor)*0.2663
        >>> visc = 0.1
        >>> rho = 860.
        >>> BearingElement.from_fluid_flow(0, nz, ntheta, nradius, length, omega, p_in,
        ...                                p_out, radius_rotor, radius_stator,
        ...                                visc, rho, eccentricity=eccentricity) # doctest: +ELLIPSIS
        BearingElement(n=0, n_link=None,
         kxx=[...
        """
        fluid_flow = flow.FluidFlow(
            nz,
            ntheta,
            nradius,
            length,
            omega,
            p_in,
            p_out,
            radius_rotor,
            radius_stator,
            visc,
            rho,
            eccentricity=eccentricity,
            load=load,
        )
        c = calculate_short_damping_matrix(fluid_flow)
        k = calculate_short_stiffness_matrix(fluid_flow)
        return cls(
            n,
            kxx=k[0],
            cxx=c[0],
            kyy=k[3],
            kxy=k[1],
            kyx=k[2],
            cyy=c[3],
            cxy=c[1],
            cyx=c[2],
            frequency=fluid_flow.omega,
        )


class SealElement(BearingElement):
    """A seal element.

    This class will create a seal element.
    Parameters can be a constant value or speed dependent.
    For speed dependent parameters, each argument should be passed
    as an array and the correspondent speed values should also be
    passed as an array.
    Values for each parameter will be interpolated for the speed.

    SealElement objects are handled differently in the Rotor class, even though it
    inherits from BearingElement class. Seal elements are not considered in static
    analysis, i.e., it does not add reaction forces (only bearings support the rotor).
    In stability level 1 analysis, seal elements are removed temporarily from the model,
    so that the cross coupled coefficients are calculated and replace the seals from
    the rotor model.
    SealElement data is stored in an individual data frame, separate from other
    bearing elements.

    Notes
    -----
    SealElement class is strongly recommended to represent seals.
    Avoid using BearingElement class for this purpose.

    Parameters
    ----------
    n: int
        Node which the bearing will be located in
    kxx: float, array, pint.Quantity
        Direct stiffness in the x direction.
    cxx: float, array, pint.Quantity
        Direct damping in the x direction.
    kyy: float, array, pint.Quantity, optional
        Direct stiffness in the y direction.
        (defaults to kxx)
    cyy: float, array, pint.Quantity, optional
        Direct damping in the y direction.
        (defaults to cxx)
    kxy: float, array, pint.Quantity, optional
        Cross coupled stiffness in the x direction.
        (defaults to 0)
    cxy: float, array, pint.Quantity, optional
        Cross coupled damping in the x direction.
        (defaults to 0)
    kyx: float, array, pint.Quantity, optional
        Cross coupled stiffness in the y direction.
        (defaults to 0)
    cyx: float, array, pint.Quantity, optional
        Cross coupled damping in the y direction.
        (defaults to 0)
    frequency: array, pint.Quantity, optional
        Array with the speeds (rad/s).
    seal_leakage: float, optional
        Amount of leakage.
    tag : str, optional
        A tag to name the element
        Default is None.
    scale_factor: float, optional
        The scale factor is used to scale the seal drawing.
        Default is 1

    Examples
    --------
    >>> # A seal element located in the first rotor node, with these
    >>> # following stiffness and damping coefficients and speed range from
    >>> # 0 to 200 rad/s
    >>> import ross as rs
    >>> kxx = 1e6
    >>> kyy = 0.8e6
    >>> cxx = 2e2
    >>> cyy = 1.5e2
    >>> frequency = np.linspace(0, 200, 11)
    >>> seal = rs.SealElement(n=0, kxx=kxx, kyy=kyy, cxx=cxx, cyy=cyy, frequency=frequency)
    >>> seal.K(frequency) # doctest: +ELLIPSIS
    array([[[1000000., 1000000., ...
    >>> seal.C(frequency) # doctest: +ELLIPSIS
    array([[[200., 200., ...
    """

    @check_units
    def __init__(
        self,
        n,
        kxx,
        cxx,
        kyy=None,
        kxy=0,
        kyx=0,
        cyy=None,
        cxy=0,
        cyx=0,
        frequency=None,
        seal_leakage=None,
        tag=None,
        scale_factor=1.0,
    ):
        super().__init__(
            n=n,
            frequency=frequency,
            kxx=kxx,
            kxy=kxy,
            kyx=kyx,
            kyy=kyy,
            cxx=cxx,
            cxy=cxy,
            cyx=cyx,
            cyy=cyy,
            tag=tag,
            scale_factor=scale_factor,
        )

        self.seal_leakage = seal_leakage
        self.color = "#77ACA2"


class BallBearingElement(BearingElement):
    """A bearing element for ball bearings.

    This class will create a bearing element based on some geometric and
    constructive parameters of ball bearings. The main difference is that
    cross-coupling stiffness and damping are not modeled in this case.

    Parameters
    ----------
    n: int
        Node which the bearing will be located in.
    n_balls: float
        Number of steel spheres in the bearing.
    d_balls: float
        Diameter of the steel sphere.
    fs: float,optional
        Static bearing loading force.
    alpha: float, optional
        Contact angle between the steel sphere and the inner / outer raceway.
    cxx: float, optional
        Direct stiffness in the x direction.
        Default is None.
    cyy: float, optional
        Direct damping in the y direction.
        Defaults is None.
    tag: str, optional
        A tag to name the element
        Default is None.
    n_link: int, optional
        Node to which the bearing will connect. If None the bearing is
        connected to ground.
        Default is None.
    scale_factor: float, optional
        The scale factor is used to scale the bearing drawing.
        Default is 1.

    Examples
    --------
    >>> n = 0
    >>> n_balls= 8
    >>> d_balls = 0.03
    >>> fs = 500.0
    >>> alpha = np.pi / 6
    >>> tag = "ballbearing"
    >>> bearing = BallBearingElement(n=n, n_balls=n_balls, d_balls=d_balls,
    ...                              fs=fs, alpha=alpha, tag=tag)
    >>> bearing.K(0)
    array([[4.64168838e+07, 0.00000000e+00],
           [0.00000000e+00, 1.00906269e+08]])
    """

    def __init__(
        self,
        n,
        n_balls,
        d_balls,
        fs,
        alpha,
        cxx=None,
        cyy=None,
        tag=None,
        n_link=None,
        scale_factor=1,
    ):

        Kb = 13.0e6
        kyy = (
            Kb
            * n_balls ** (2.0 / 3)
            * d_balls ** (1.0 / 3)
            * fs ** (1.0 / 3)
            * (np.cos(alpha)) ** (5.0 / 3)
        )

        nb = [8, 12, 16]
        ratio = [0.46, 0.64, 0.73]
        dict_ratio = dict(zip(nb, ratio))

        if n_balls in dict_ratio.keys():
            kxx = dict_ratio[n_balls] * kyy
        else:
            f = interpolate.interp1d(nb, ratio, kind="quadratic")
            kxx = f(n_balls)

        if cxx is None:
            cxx = 1.25e-5 * kxx
        if cyy is None:
            cyy = 1.25e-5 * kyy

        super().__init__(
            n=n,
            frequency=None,
            kxx=kxx,
            kxy=0.0,
            kyx=0.0,
            kyy=kyy,
            cxx=cxx,
            cxy=0.0,
            cyx=0.0,
            cyy=cyy,
            tag=tag,
            n_link=n_link,
            scale_factor=scale_factor,
        )

        self.color = "#77ACA2"


class RollerBearingElement(BearingElement):
    """A bearing element for roller bearings.

    This class will create a bearing element based on some geometric and
    constructive parameters of roller bearings. The main difference is that
    cross-coupling stiffness and damping are not modeled in this case.

    Parameters
    ----------
    n: int
        Node which the bearing will be located in.
    n_rollers: float
        Number of steel spheres in the bearing.
    l_rollers: float
        Length of the steel rollers.
    fs: float,optional
        Static bearing loading force.
    alpha: float, optional
        Contact angle between the steel sphere and the inner / outer raceway.
    cxx: float, optional
        Direct stiffness in the x direction.
        Default is None.
    cyy: float, optional
        Direct damping in the y direction.
        Defaults is None.
    tag: str, optional
        A tag to name the element
        Default is None.
    n_link: int, optional
        Node to which the bearing will connect. If None the bearing is
        connected to ground.
        Default is None.
    scale_factor: float, optional
        The scale factor is used to scale the bearing drawing.
        Default is 1.

    Examples
    --------
    >>> n = 0
    >>> n_rollers= 8
    >>> l_rollers = 0.03
    >>> fs = 500.0
    >>> alpha = np.pi / 6
    >>> tag = "rollerbearing"
    >>> bearing = RollerBearingElement(n=n, n_rollers=n_rollers, l_rollers=l_rollers,
    ...                            fs=fs, alpha=alpha, tag=tag)
    >>> bearing.K(0)
    array([[2.72821927e+08, 0.00000000e+00],
           [0.00000000e+00, 5.56779444e+08]])
    """

    def __init__(
        self,
        n,
        n_rollers,
        l_rollers,
        fs,
        alpha,
        cxx=None,
        cyy=None,
        tag=None,
        n_link=None,
        scale_factor=1,
    ):

        Kb = 1.0e9
        kyy = (
            Kb
            * n_rollers ** 0.9
            * l_rollers ** 0.8
            * fs ** 0.1
            * (np.cos(alpha)) ** 1.9
        )

        nr = [8, 12, 16]
        ratio = [0.49, 0.66, 0.74]
        dict_ratio = dict(zip(nr, ratio))

        if n_rollers in dict_ratio.keys():
            kxx = dict_ratio[n_rollers] * kyy
        else:
            f = interpolate.interp1d(nr, ratio, kind="quadratic")
            kxx = f(n_rollers)

        if cxx is None:
            cxx = 1.25e-5 * kxx
        if cyy is None:
            cyy = 1.25e-5 * kyy

        super().__init__(
            n=n,
            frequency=None,
            kxx=kxx,
            kxy=0.0,
            kyx=0.0,
            kyy=kyy,
            cxx=cxx,
            cxy=0.0,
            cyx=0.0,
            cyy=cyy,
            tag=tag,
            n_link=n_link,
            scale_factor=scale_factor,
        )

        self.color = "#77ACA2"


class MagneticBearingElement(BearingElement):
    """Magnetic bearing.

    This class creates a magnetic bearing element.
    Converts electromagnetic parameters and PID gains to stiffness and damping
    coefficients.

    Parameters
    ----------
    n : int
        The node in which the magnetic bearing will be located in the rotor.
    g0: float
        Air gap in m^2.
    i0: float
        Bias current in Ampere
    ag: float
        Pole area in m^2.
    nw: float or int
        Number of windings
    alpha: float or int
        Pole angle in radians.
    kp_pid: float or int
        Proportional gain of the PID controller.
    kd_pid: float or int
        Derivative gain of the PID controller.
    k_amp: float or int
        Gain of the amplifier model.
    k_sense: float or int
        Gain of the sensor model.
    tag: str, optional
        A tag to name the element
        Default is None.
    n_link: int, optional
        Node to which the bearing will connect. If None the bearing is
        connected to ground.
        Default is None.
    scale_factor: float, optional
        The scale factor is used to scale the bearing drawing.
        Default is 1.

    ----------
    See the following reference for the electromagnetic parameters g0, i0, ag, nw, alpha:
    Book: Magnetic Bearings. Theory, Design, and Application to Rotating Machinery
    Authors: Gerhard Schweitzer and Eric H. Maslen
    Page: 84-95

    Examples
    --------
    >>> n = 0
    >>> g0 = 1e-3
    >>> i0 = 1.0
    >>> ag = 1e-4
    >>> nw = 200
    >>> alpha = 0.392
    >>> kp_pid = 1.0
    >>> kd_pid = 1.0
    >>> k_amp = 1.0
    >>> k_sense = 1.0
    >>> tag = "magneticbearing"
    >>> mbearing = MagneticBearingElement(n=n, g0=g0, i0=i0, ag=ag, nw=nw,alpha=alpha,
    ...                                   kp_pid=kp_pid, kd_pid=kd_pid, k_amp=k_amp,
    ...                                   k_sense=k_sense)
    >>> mbearing.kxx
    [-4640.623377181318]
    """

    def __init__(
        self,
        n,
        g0,
        i0,
        ag,
        nw,
        alpha,
        kp_pid,
        kd_pid,
        k_amp,
        k_sense,
        tag=None,
        n_link=None,
        scale_factor=1,
    ):
        pL = [g0, i0, ag, nw, alpha, kp_pid, kd_pid, k_amp, k_sense]
        pA = [0, 0, 0, 0, 0, 0, 0, 0, 0]

        # Check if it is a number or a list with 2 items
        for i in range(9):
            if type(pL[i]) == float or int:
                pA[i] = np.array(pL[i])
            else:
                if type(pL[i]) == list:
                    if len(pL[i]) > 2:
                        raise ValueError(
                            "Parameters must be scalar or a list with 2 items"
                        )
                    else:
                        pA[i] = np.array(pL[i])
                else:
                    raise ValueError("Parameters must be scalar or a list with 2 items")

        # From: "Magnetic Bearings. Theory, Design, and Application to Rotating Machinery"
        # Authors: Gerhard Schweitzer and Eric H. Maslen
        # Page: 354
        ks = (
            -4.0
            * pA[1] ** 2.0
            * np.cos(pA[4])
            * 4.0
            * np.pi
            * 1e-7
            * pA[3] ** 2.0
            * pA[2]
            / (4.0 * pA[0] ** 3)
        )
        ki = (
            4.0
            * pA[1]
            * np.cos(pA[4])
            * 4.0
            * np.pi
            * 1e-7
            * pA[3] ** 2.0
            * pA[2]
            / (4.0 * pA[0] ** 2)
        )
        k = ki * pA[7] * pA[8] * (pA[5] + np.divide(ks, ki * pA[7] * pA[8]))
        c = ki * pA[7] * pA[5] * pA[8]
        # k = ki * k_amp*k_sense*(kp_pid+ np.divide(ks, ki*k_amp*k_sense))
        # c = ki*k_amp*kd_pid*k_sense

        # Get the parameters from k and c
        if np.isscalar(k):
            # If k is scalar, symmetry is assumed
            kxx = k
            kyy = k
        else:
            kxx = k[0]
            kyy = k[1]

        if np.isscalar(c):
            # If c is scalar, symmetry is assumed
            cxx = c
            cyy = c
        else:
            cxx = c[0]
            cyy = c[1]

        super().__init__(
            n=n,
            frequency=None,
            kxx=kxx,
            kxy=0.0,
            kyx=0.0,
            kyy=kyy,
            cxx=cxx,
            cxy=0.0,
            cyx=0.0,
            cyy=cyy,
            tag=tag,
            n_link=n_link,
            scale_factor=scale_factor,
        )


class BearingElement6DoF(BearingElement):
    """A generalistic 6 DoF bearing element.

    This class will create a bearing
    element based on the user supplied stiffness and damping coefficients. These
    are determined alternatively, via purposefully built codes.

    Parameters
    ----------
    kxx: float, array, pint.Quantity
        Direct stiffness in the x direction.
    cxx: float, array, pint.Quantity
        Direct damping in the x direction.
    kyy: float, array, pint.Quantity, optional
        Direct stiffness in the y direction.
        Defaults to kxx
    cyy: float, array, pint.Quantity, optional
        Direct damping in the y direction.
        Default is to cxx
    kxy: float, array, pint.Quantity, optional
        Cross stiffness between xy directions.
        Default is 0
    kyx: float, array, pint.Quantity, optional
        Cross stiffness between yx directions.
        Default is 0
    kzz: float, array, pint.Quantity, optional
        Direct stiffness in the z direction.
        Default is 0
    cxy: float, array, pint.Quantity, optional
        Cross damping between xy directions.
        Default is 0
    cyx: float, array, pint.Quantity, optional
        Cross damping between yx directions.
        Default is 0
    czz: float, array, pint.Quantity, optional
        Direct damping in the z direction.
        Default is 0
    frequency: array, pint.Quantity, optional
        Array with the frequencies (rad/s).
    tag : str, optional
        A tag to name the element
        Default is None
    n_link: int, optional
        Node to which the bearing will connect. If None the bearing is
        connected to ground.
        Default is None.
    scale_factor: float, optional
        The scale factor is used to scale the bearing drawing.
        Default is 1.

    Examples
    --------
    >>> n = 0
    >>> kxx = 1.0e7
    >>> kyy = 1.5e7
    >>> kzz = 5.0e5
    >>> bearing = BearingElement6DoF(n=n, kxx=kxx, kyy=kyy, kzz=kzz,
    ...                              cxx=0, cyy=0)
    >>> bearing.K(0)
    array([[10000000.,        0.,        0.],
           [       0., 15000000.,        0.],
           [       0.,        0.,   500000.]])
    """

    @check_units
    def __init__(
        self,
        n,
        kxx,
        cxx,
        kyy=None,
        cyy=None,
        kxy=0.0,
        kyx=0.0,
        kzz=0.0,
        cxy=0.0,
        cyx=0.0,
        czz=0.0,
        frequency=None,
        tag=None,
        n_link=None,
        scale_factor=1,
        color="#355d7a",
    ):
        super().__init__(
            n=n,
            kxx=kxx,
            cxx=cxx,
            kyy=kyy,
            kxy=kxy,
            kyx=kyx,
            cyy=cyy,
            cxy=cxy,
            cyx=cyx,
            frequency=frequency,
            tag=tag,
            n_link=n_link,
            scale_factor=scale_factor,
            color=color,
        )

        new_args = ["kzz", "czz"]

        args_dict = locals()
        coefficients = {}

        if kzz is None:
            args_dict["kzz"] = (
                kxx * 0.6
            )  # NSK manufacturer sugestion for deep groove ball bearings
        if czz is None:
            args_dict["czz"] = cxx

        for arg in new_args:
            if arg[0] == "k":
                coefficients[arg] = _Stiffness_Coefficient(
                    coefficient=args_dict[arg], frequency=None
                )
            else:
                coefficients[arg] = _Damping_Coefficient(args_dict[arg], None)

        coefficients_len = [len(v.coefficient) for v in coefficients.values()]

        for c in coefficients_len:
            if c != 1:
                raise ValueError(
                    "Arguments (coefficients and frequency)"
                    " must have the same dimension"
                )

        for k, v in coefficients.items():
            setattr(self, k, v)

    def __hash__(self):
        return hash(self.tag)

    def __repr__(self):
        """Return a string representation of a bearing element.

        Returns
        -------
        A string representation of a bearing element object.

        Examples
        --------
        >>> bearing = bearing_example()
        >>> bearing # doctest: +ELLIPSIS
        BearingElement(n=0, n_link=None,
         kxx=[...
        """
        return (
            f"{self.__class__.__name__}"
            f"(n={self.n}, n_link={self.n_link},\n"
            f" kxx={self.kxx}, kxy={self.kxy},\n"
            f" kyx={self.kyx}, kyy={self.kyy},\n"
            f" kzz={self.kzz}, cxx={self.cxx},\n"
            f" cxy={self.cxy}, cyx={self.cyx},\n"
            f" cyy={self.cyy}, czz={self.czz},\n"
            f" frequency={self.frequency}, tag={self.tag!r})"
        )

    def __eq__(self, other):
        """Equality method for comparasions.

        Parameters
        ----------
        other : object
            The second object to be compared with.

        Returns
        -------
        bool
            True if the comparison is true; False otherwise.

        Examples
        --------
        >>> bearing1 = bearing_example()
        >>> bearing2 = bearing_example()
        >>> bearing1 == bearing2
        True
        """
        compared_attributes = [
            "kxx",
            "kyy",
            "kxy",
            "kyx",
            "cxx",
            "cyy",
            "cxy",
            "cyx",
            "kzz",
            "czz",
            "frequency",
            "n",
            "n_link",
        ]
        if isinstance(other, self.__class__):
            return all(
                (
                    np.array(getattr(self, attr)).all()
                    == np.array(getattr(other, attr)).all()
                    for attr in compared_attributes
                )
            )
        return False

    def save(self, file):
        try:
            data = toml.load(file)
        except FileNotFoundError:
            data = {}

        args = {
            "n": self.n,
            "kxx": [float(i) for i in self.kxx.coefficient],
            "cxx": [float(i) for i in self.cxx.coefficient],
            "kyy": [float(i) for i in self.kyy.coefficient],
            "kxy": [float(i) for i in self.kxy.coefficient],
            "kyx": [float(i) for i in self.kyx.coefficient],
            "kzz": [float(i) for i in self.kzz.coefficient],
            "cyy": [float(i) for i in self.cyy.coefficient],
            "cxy": [float(i) for i in self.cxy.coefficient],
            "cyx": [float(i) for i in self.cyx.coefficient],
            "czz": [float(i) for i in self.czz.coefficient],
            "tag": self.tag,
            "n_link": self.n_link,
            "scale_factor": self.scale_factor,
        }
        if self.frequency is not None:
            args["frequency"] = [float(i) for i in self.frequency]
        else:
            args["frequency"] = self.frequency

        data[f"{self.__class__.__name__}_{self.tag}"] = args

        with open(file, "w") as f:
            toml.dump(data, f)

    def dof_mapping(self):
        """Degrees of freedom mapping.

        Returns a dictionary with a mapping between degree of freedom and its index.

        Returns
        -------
        dof_mapping : dict
            A dictionary containing the degrees of freedom and their indexes.

        Examples
        --------
        The numbering of the degrees of freedom for each node.

        Being the following their ordering for a node:

        x_0 - horizontal translation
        y_0 - vertical translation
        z_0 - axial translation

        >>> bearing = bearing_6dof_example()
        >>> bearing.dof_mapping()
        {'x_0': 0, 'y_0': 1, 'z_0': 2}
        """
        return dict(x_0=0, y_0=1, z_0=2)

    def K(self, frequency):
        """Stiffness matrix for an instance of a bearing element.

        This method returns the stiffness matrix for an instance of a bearing element.

        Parameters
        ----------
        frequency : float
            The excitation frequency.

        Returns
        -------
        K : np.ndarray
            A 3x3 matrix of floats containing the kxx, kxy, kyx, kyy and kzz values.

        Examples
        --------
        >>> bearing = bearing_6dof_example()
        >>> bearing.K(0)
        array([[1000000.,       0.,       0.],
               [      0.,  800000.,       0.],
               [      0.,       0.,  100000.]])
        """
        kxx = self.kxx.interpolated(frequency)
        kyy = self.kyy.interpolated(frequency)
        kxy = self.kxy.interpolated(frequency)
        kyx = self.kyx.interpolated(frequency)
        kzz = self.kzz.interpolated(frequency)

        K = np.array([[kxx, kxy, 0], [kyx, kyy, 0], [0, 0, kzz]])

        return K

    def C(self, frequency):
        """Damping matrix for an instance of a bearing element.

        This method returns the damping matrix for an instance of a bearing element.

        Parameters
        ----------
        frequency : float
            The excitation frequency.

        Returns
        -------
        C: np.ndarray
            A 3x3 matrix of floats containing the cxx, cxy, cyx, cyy, and czz values.

        Examples
        --------
        >>> bearing = bearing_6dof_example()
        >>> bearing.C(0)
        array([[200.,   0.,   0.],
               [  0., 150.,   0.],
               [  0.,   0.,  50.]])
        """
        cxx = self.cxx.interpolated(frequency)
        cyy = self.cyy.interpolated(frequency)
        cxy = self.cxy.interpolated(frequency)
        cyx = self.cyx.interpolated(frequency)
        czz = self.czz.interpolated(frequency)

        C = np.array([[cxx, cxy, 0], [cyx, cyy, 0], [0, 0, czz]])

        return C


def bearing_example():
    """Create an example of bearing element.

    This function returns an instance of a simple seal. The purpose is to make
    available a simple model so that doctest can be written using it.

    Returns
    -------
    An instance of a bearing object.

    Examples
    --------
    >>> bearing = bearing_example()
    >>> bearing.frequency[0]
    0.0
    """
    w = np.linspace(0, 200, 11)
    bearing = BearingElement(n=0, kxx=1e6, kyy=0.8e6, cxx=2e2, cyy=1.5e2, frequency=w)
    return bearing


def seal_example():
    """Create an example of seal element.

    This function returns an instance of a simple seal. The purpose is to make
    available a simple model so that doctest can be written using it.

    Returns
    -------
    seal : ross.SealElement
        An instance of a bearing object.

    Examples
    --------
    >>> seal = bearing_example()
    >>> seal.frequency[0]
    0.0
    """
    w = np.linspace(0, 200, 11)
    seal = SealElement(n=0, kxx=1e6, kyy=0.8e6, cxx=2e2, cyy=1.5e2, frequency=w)
    return seal


def bearing_6dof_example():
    """Create an example of bearing element.

    This function returns an instance of a simple bearing. The purpose is to make
    available a simple model so that doctest can be written using it.

    Returns
    -------
    bearing : ross.BearingElement6DoF
        An instance of a bearing object.

    Examples
    --------
    >>> bearing = bearing_example()
    >>> bearing.frequency[0]
    0.0
    """
    bearing = BearingElement6DoF(
        n=0, kxx=1e6, kyy=0.8e6, cxx=2e2, cyy=1.5e2, kzz=1e5, czz=0.5e2
    )
    return bearing