Source code for gemseo.mlearning.linear_model_fitting.null_space

# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

"""Null space algorithm."""

from __future__ import annotations

from typing import TYPE_CHECKING

from numpy import vstack
from scipy.linalg import lstsq
from scipy.linalg import qr

from gemseo.mlearning.linear_model_fitting.base_linear_model_fitter import (
    BaseLinearModelFitter,
)
from gemseo.mlearning.linear_model_fitting.base_linear_model_fitter import (
    _WrappedFittingFunction,
)
from gemseo.mlearning.linear_model_fitting.null_space_settings import NullSpace_Settings

if TYPE_CHECKING:
    from gemseo.typing import RealArray


class _NullSpaceFittingFunction(_WrappedFittingFunction):
    """Null space method."""

    def fit(
        self,
        input_data: RealArray,
        output_data: RealArray,
        *extra_data: tuple[RealArray, RealArray],
    ) -> RealArray:
        """
        Raises:
            ValueError: When the extra data are missing.
        """  # noqa: D205, D212
        if not extra_data:
            msg = "The null space algorithm requires extra data."
            raise ValueError(msg)

        # We use the notations of Ghisu et al (2021).
        A = input_data  # noqa: N806
        b = output_data
        C = vstack([x[0] for x in extra_data])  # noqa: N806
        d = vstack([x[1] for x in extra_data])
        t, n = A.shape

        Q, R = qr(A.T)  # noqa: N806
        Q1 = Q[:, 0:t]  # noqa: N806
        Q2 = Q[:, t:n]  # noqa: N806
        w1 = lstsq(R.T[0:t, 0:t], b)[0]
        G = C @ Q2  # noqa: N806
        h = d - C @ Q1 @ w1
        w2 = lstsq(G, h)[0]
        return (Q1 @ w1 + Q2 @ w2).T


[docs] class NullSpace(BaseLinearModelFitter[_NullSpaceFittingFunction, NullSpace_Settings]): r"""The null space method. Given the linear model fitting problem presented in :mod:`this page <.linear_model_fitting>`, this algorithm uses the null space method to solve a penalized least squares problem of the form: .. math:: min_w \|\tilde{Y} - \tilde{X}w\|_2 \quad s.t. \quad Y=Xw where :math:`\tilde{X}` and :math:`\tilde{Y}` contained additional data such that :math:`\text{rank}\left(\begin{matrix}X\\\tilde{X}\end{matrix}\right)` equals the number of features :math:`d`. This method was applied by Ghisu *et al* (2021) to fit a polynomial chaos expansion from input, output and Jacobian data. Tiziano Ghisu, Diego I. Lopez, Pranay Seshadri and Shahrokh Shahpar, *Gradient-enhanced Least-square Polynomial Chaos Expansions for Uncertainty Quantification and Robust Optimization*, AIAA 2021-3073. AIAA AVIATION 2021 FORUM. August 2021. """ Settings = NullSpace_Settings _FITTER_CLASS = _NullSpaceFittingFunction def _fit( self, input_data: RealArray, output_data: RealArray, *extra_data: tuple[RealArray, RealArray], ) -> RealArray: return self._fitter.fit(input_data, output_data, *extra_data)