diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index dcbe783..abd6f73 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -18,14 +18,13 @@ jobs: - name: Install dependencies run: | pip install sphinx sphinx_rtd_theme myst_parser - # WICHTIG: Wenn dein Code Abhängigkeiten hat (z.B. numpy, scipy), - # installiere sie hier, sonst kann 'autodoc' den Code nicht importieren! - # pip install -e . + # WICHTIG: autodoc importiert den Code, um die Docstrings zu lesen. + # Ohne diese Zeile schlaegt der Import fehl und die Seiten bleiben leer. + pip install -e . - name: Sphinx build run: | cd docs - sphinx-apidoc -o ./source ../src -f make html - name: Deploy to GitHub Pages diff --git a/docs/source/Analysis.rst b/docs/source/Analysis.rst index b866568..39bb3dd 100644 --- a/docs/source/Analysis.rst +++ b/docs/source/Analysis.rst @@ -1,18 +1,8 @@ -Analysis -======================= - -.. py:function:: get_variances(a, lam = None, Dict = False) - - This function returns the variances of the approximated ANOVA terms for all lam, if lam == None. Otherwise for the provided lam. Depending on Dict, it returns the approximated ANOVA terms as a vector or as a dict. - -.. py:function:: get_GSI(a, lam = None, Dict = False) - - This function returns the global sensitivity indices of the approximation for all lam, if lam == None. Otherwise for the provided lam. Depending on Dict, it returns the approximated ANOVA terms as a vector or as a dict. - -.. py:function:: get_AttributeRanking(a, lam = None) - - This function returns the attribute ranking of the approximation for all reg. parameters lam, if lam == None, as a dictionary of vectors of length ``a.d``. Otherwise for the provided lam as a vector of length ``a.d``. - -.. py:function:: get_ShapleyValues(a, lam = None) - - This function returns the Shapley values of the approximation for all reg. parameters lam, if lam == None, as a dictionary of vectors of length ``a.d``. Otherwise for the provided lam as a vector of length ``a.d``. +Analysis +======== + +.. automodule:: pyANOVAapprox.analysis + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource diff --git a/docs/source/Approximation.rst b/docs/source/Approximation.rst index 9353ed2..9c2b3e6 100644 --- a/docs/source/Approximation.rst +++ b/docs/source/Approximation.rst @@ -1,107 +1,17 @@ -Approximation -======================= - -.. py:class:: approx(X, y, U, N, basis="cos") - - A class to hold the scattered data function approximation. - - This class represents an approximation object constructed from scattered data nodes, function values, an ANOVA decomposition class, and a choice of basis functions. It supports multiple bases and regularization parameters. - - .. rubric:: Attributes: - - .. py:attribute:: basis - - **Type:** `str` - - Basis of the function space. Supported values are: - - - `"per"`: exponential functions - - `"cos"`: cosine functions - - `"cheb"`: Chebyshev basis - - `"std"`: transformed exponential functions - - `"chui1"`: Haar wavelets - - `"chui2"`: Chui-Wang wavelets of order 2 - - `"chui3"`: Chui-Wang wavelets of order 3 - - `"chui4"`: Chui-Wang wavelets of order 4 - - .. py:attribute:: X - - **Type:** `Numpy array wit dtype float` - - The scattered data nodes with `M` rows and `d` columns. - - .. py:attribute:: y - - **Type:** `Numpy array wit dtype float or complex` - - A vector of `M` function values. Complex-valued for `basis = "per"`, real-valued otherwise. - - .. py:attribute:: U - - **Type:** `List[Tuple[int]]` - - A vector containing subsets of coordinate indices representing the ANOVA decomposition. - - .. py:attribute:: N - - **Type:** `List[int]` - - Bandwidths for each ANOVA term. - - .. py:attribute:: trafo - - **Type:** `GroupedTransform` - - Holds the grouped transformation. - - .. py:attribute:: fc - - **Type:** `Dict[float, GroupedCoefficients]` - - holds the GroupedCoefficients after approximation for every different regularization parameters - - .. rubric:: Constructor: - - .. py:method:: approx(X, y, U, N, basis="cos") - - - .. rubric:: Additional Constructor: - - .. py:method:: approx(X, y, ds, N, basis="cos") - - - .. rubric:: Functions: - - .. py:method:: approximate(self, lam, max_iter=50, weights=None, verbose=False, solver=None, tol=1e-8) - - If ``lam`` is a ``np.ndarray`` of dtype float, this function computes the approximation for the regularization parameters contained in ``lam``. - - If ``lam`` is a float, this function computes the approximation for the regularization parameter ``lam``. - - .. py:method:: evaluate(self, lam=None, X=None) - - This function evaluates the approximation with optional node matrix ``X`` and regularization ``lam``. - - - If both ``X`` and ``lam`` are given: evaluate at ``X`` for specific ``lam``. - - If only ``X`` is given: evaluate at ``X`` for all ``lam``. - - If only ``lam`` is given: evaluate at ``self.X`` for specific ``lam``. - - If neither are given: evaluate at ``self.X`` for all ``lam``. - - .. py:method:: evaluateANOVAterms(self, X, lam=None) - - This function evaluates the single ANOVA terms of the approximation on the nodes of matrix ``X`` and regularization ``lam``. - - - If ``lam`` is given: evaluate at ``X`` for specific ``lam``. - - If ``lam`` is not given: evaluate at ``X`` for all ``lam``. - - .. py:method:: evaluateSHAPterms(self, X, lam=None) - - This function evaluates for each dimension the Shapley contribution to the overall approximation on the nodes of matrix ``X`` and regularization ``lam``. - - - If ``lam`` is given: evaluate at ``X`` for specific ``lam``. - - If ``lam`` is not given: evaluate at ``X`` for all ``lam``. - - - - - +Approximation +============= + +.. automodule:: pyANOVAapprox.approx + :no-members: + +.. autoclass:: pyANOVAapprox.approx.approx + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + +.. autoclass:: pyANOVAapprox.approx.approx_setting + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css new file mode 100644 index 0000000..0dd043f --- /dev/null +++ b/docs/source/_static/custom.css @@ -0,0 +1,26 @@ +/* Custom overrides to widen the main content area for sphinx_rtd_theme */ + +/* Increase the maximum content width */ +.wy-nav-content, +.rst-content { + max-width: 100vw; + height: 100vh; +} + +/* Slightly reduce left padding for large screens so content uses more space */ +@media (min-width: 1200px) { + .wy-nav-content { + padding-left: 20px; + padding-right: 20px; + } +} + +/* Ensure the main document content uses more horizontal space */ +.rst-content .section, .document { + margin-left: 0; + margin-right: 0; +} + +.rubric { + font-size: 20px; +} diff --git a/docs/source/conf.py b/docs/source/conf.py index e05a3b7..e1a2ba2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,12 +26,28 @@ "sphinx.ext.autodoc", "sphinx.ext.napoleon", # Unterstützt Google/NumPy-Style Docstrings "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", "myst_parser", ] -templates_path = ["_templates"] +templates_path = [] exclude_patterns = [] +# -- autodoc ----------------------------------------------------------------- +autodoc_default_options = { + "members": True, + "undoc-members": True, + "show-inheritance": True, + "member-order": "bysource", +} + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "scipy": ("https://docs.scipy.org/doc/scipy/", None), +} + # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output @@ -39,3 +55,11 @@ # Auf das im Workflow genutzte Theme umgestellt html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] +html_theme_options = { + "collapse_navigation": False, + "sticky_navigation": True, + "navigation_depth": 5, +} +html_css_files = [ + "custom.css", +] diff --git a/docs/source/index.rst b/docs/source/index.rst index 15b828f..3e8a582 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,5 +16,5 @@ documentation for details. :caption: Contents: Analysis - modules + Approximation Errors \ No newline at end of file diff --git a/src/pyANOVAapprox/analysis.py b/src/pyANOVAapprox/analysis.py index aedd105..e5b0942 100644 --- a/src/pyANOVAapprox/analysis.py +++ b/src/pyANOVAapprox/analysis.py @@ -1,3 +1,19 @@ +"""Utilities for analyzing an ANOVA approximation. + +This module provides functions for computing variances, global sensitivity +indices, attribute rankings, active sets and Shapley values from an +:py:class:`~pyANOVAapprox.approx.approx` object. + +The functions are bound as methods onto :py:class:`~pyANOVAapprox.approx.approx`, +so their first argument ``self`` is the approximation object. They are also +available as plain functions from the package root, where the same argument is +named ``a``:: + + import pyANOVAapprox as anova + + anova.get_variances(a, lam=1e-3) # equivalent to a.get_variances(lam=1e-3) +""" + # from pyANOVAapprox import * import numpy as np @@ -26,8 +42,24 @@ def _variances(self, settingnr, lam, Dict): # helpfunction for get_variances def get_variances(self, settingnr=None, lam=None, Dict=False): - """ - This function returns the variances of the approximated ANOVA terms for all ``\lambda``, if lam == None. Otherwise for the provided lam. Depending on Dict, it returns the approximated ANOVA terms as a vector or as a dict. + r"""Compute the variances of all ANOVA terms in the approximation. + + The variance of an ANOVA term measures how much that term contributes to + the total variance of the approximated function. + + Args: + self: The approximation object. + settingnr (int, optional): Defaults to ``None``. Index of the approximation setting to use. + Defaults to the only setting if just one is present. + lam (float, optional): Defaults to ``None``. Regularization parameter. If omitted, the + variances are computed for every available :math:`\lambda`. + Dict (bool, optional): Defaults to ``False``. If ``True``, return the variances as a dictionary indexed + by ANOVA terms. Otherwise return them as an array. + + Returns: + dict or numpy.ndarray: Variances of the ANOVA terms for the requested + ``lam``. If ``lam`` is omitted, a dictionary mapping each + :math:`\lambda` to the corresponding result. """ if isinstance(lam, float): return self._variances( @@ -66,8 +98,23 @@ def _GSI(self, settingnr, lam, Dict): # helpfunction for get_GSI def get_GSI(self, settingnr=None, lam=None, Dict=False): - """ - This function returns the global sensitivity indices of the approximation for all ``\lambda``, if lam == None. Otherwise for the provided lam. Depending on Dict, it returns the approximated ANOVA terms as a vector or as a dict. + r"""Compute the Global Sensitivity Indices (GSIs) of the approximation. + + A Global Sensitivity Index represents the fraction of the total variance + explained by each ANOVA term. + + Args: + self: The approximation object. + settingnr (int, optional): Defaults to ``None``. Index of the approximation setting to use. + lam (float, optional): Defaults to ``None``. Regularization parameter. If omitted, the GSIs + are computed for every available :math:`\lambda`. + Dict (bool, optional): Defaults to ``False``. If ``True``, return the indices as a dictionary indexed by + ANOVA terms. Otherwise return them as an array. + + Returns: + dict or numpy.ndarray: Global Sensitivity Indices of the ANOVA terms + for the requested ``lam``. If ``lam`` is omitted, a dictionary mapping + each :math:`\lambda` to the corresponding result. """ if ( @@ -109,8 +156,22 @@ def _AttributeRanking(self, settingnr, lam): # helpfunction for get_AttributeRa def get_AttributeRanking(self, settingnr=None, lam=None): - """ - This function returns the attribute ranking of the approximation for all reg. parameters ``\lambda``, if lam == None, as a dictionary of vectors of length `a.d`. Otherwise for the provided lam as a vector of length `a.d`. + r"""Rank the input variables by their influence on the approximation. + + Higher-ranked variables contribute more strongly to the approximated + function. The ranking is derived from the global sensitivity indices by + distributing each ANOVA term's index over the variables it involves. + + Args: + self: The approximation object. + settingnr (int, optional): Defaults to ``None``. Index of the approximation setting to use. + lam (float, optional): Defaults to ``None``. Regularization parameter. If omitted, the + ranking is computed for every available :math:`\lambda`. + + Returns: + dict or numpy.ndarray: For a given ``lam``, an array of length ``d``. + If ``lam`` is omitted, a dictionary mapping each :math:`\lambda` to + such an array. """ if ( lam is None @@ -150,7 +211,30 @@ def _ActiveSet(self, eps, settingnr, lam): # helpfunction for get_ActiveSet def get_ActiveSet(self, eps, settingnr=None, lam=None): - + r"""Select the ANOVA terms whose sensitivity exceeds a per-order threshold. + + A term ``u`` is kept when its global sensitivity index is larger than + ``eps[len(u) - 1]``, i.e. the threshold is chosen according to the + interaction order of the term. The returned set always starts with the + empty term ``()``. + + Args: + self: The approximation object. + eps (numpy.ndarray): Defaults to ``None``. Thresholds, one per interaction order. Its length + must equal the maximum interaction order ``ds`` of the setting. + settingnr (int, optional): Defaults to ``None``. Index of the approximation setting to use. + lam (float, optional): Regularization parameter. If omitted, the active + set is computed for every available :math:`\lambda`. + + Returns: + dict or list: For a given ``lam``, the list of retained ANOVA terms. + If ``lam`` is omitted, a dictionary mapping each :math:`\lambda` to + such a list. + + Raises: + ValueError: If ``len(eps)`` does not match the maximum interaction + order of the setting. + """ if ( lam is None ): # get_ActiveSet(a::approx, eps::Vector{Float64})::Dict{Float64,Vector{Vector{Int}}} @@ -175,8 +259,22 @@ def _ShapleyValues(self, settingnr, lam): # helpfunction for get_ShapleyValues def get_ShapleyValues(self, settingnr=None, lam=None): - """ - This function returns the Shapley values of the approximation for all reg. parameters ``\lambda``, if lam == None, as a dictionary of vectors of length `a.d`. Otherwise for the provided lam as a vector of length `a.d`. + r"""Compute the Shapley values of the approximation. + + Shapley values provide a fair allocation of the total contribution of the + approximation among the individual input variables, by distributing the + contributions of all ANOVA interaction terms. + + Args: + self: The approximation object. + settingnr (int, optional): Defaults to ``None``. Index of the approximation setting to use. + lam (float, optional): Defaults to ``None``. Regularization parameter. If omitted, the + Shapley values are computed for every available :math:`\lambda`. + + Returns: + dict or numpy.ndarray: For a given ``lam``, an array of length ``d``. + If ``lam`` is omitted, a dictionary mapping each :math:`\lambda` to + such an array. """ if lam is None: # get_ShapleyValues(a::approx)::Dict{Float64,Vector{Float64}} return {l: self._ShapleyValues(settingnr, l) for l in self.lam.keys()} diff --git a/src/pyANOVAapprox/approx.py b/src/pyANOVAapprox/approx.py index 0755036..cdf32e5 100644 --- a/src/pyANOVAapprox/approx.py +++ b/src/pyANOVAapprox/approx.py @@ -1,3 +1,15 @@ +"""Scattered data function approximation with an ANOVA decomposition. + +The central object is :py:class:`approx`, which holds the nodes, the function +values and one or more :py:class:`approx_setting` objects describing the ANOVA +decomposition and basis. Fitting is done with +:py:meth:`approx.approximate`, evaluation with :py:meth:`approx.evaluate`. + +The analysis routines (variances, sensitivity indices, Shapley values) and the +error measures are defined in :py:mod:`pyANOVAapprox.analysis` and +:py:mod:`pyANOVAapprox.errors` and attached to :py:class:`approx` as methods. +""" + import copy import warnings @@ -76,6 +88,37 @@ def transformX(X, basis): class approx_setting: + r"""One ANOVA decomposition and basis choice belonging to an :py:class:`approx`. + + An :py:class:`approx` holds a list of these, so that several decompositions + can be fitted against the same nodes and function values. Settings are + normally created for you by :py:class:`approx`; you rarely construct one + directly. + + Attributes: + basis (str): Basis of the function space. See :py:class:`approx` for + the list of supported values. + U (list[tuple[int]]): Subsets of coordinate indices making up the + ANOVA decomposition. + N (list[numpy.ndarray]): Bandwidths, one array per ANOVA term. + classification (bool): Whether the setting describes a classification + rather than a regression problem. + basis_vect (list[str]): Per-dimension basis choice, used by the + ``"mixed"`` basis. + algorithm (str): ``"nfft"``, ``"keops"`` or ``"direct"``. + device (str): Device used by the ``"keops"`` algorithm, allows user to manually switch between GPU or CPU computation. Supported values are: ``"cuda"`` + for NVIDIA and CUDA-supporting devices, ``"mps"`` for Apple MPS-supporting devices, ``"cpu"`` for selecting CPU processing. + If ``None`` (default), the algorithm automatically selects the device based on availability, prioritizing GPU use. + parallel (bool): Whether sub-transforms run threaded. Forced to + ``False`` for any algorithm other than ``"nfft"``. + lam (set[float]): Regularization parameters associated with the setting. + parent (approx): The approximation this setting belongs to. + + Raises: + ValueError: If ``basis`` is not a supported basis, or if ``N`` has + neither ``|U|`` nor ``max |u|`` entries. + """ + def __init__( self, parent, @@ -84,7 +127,8 @@ def __init__( basis="cos", classification=False, basis_vect=[], - fastmult=None, + algorithm="direct", + device=None, parallel=True, ds=None, lam={0.0}, @@ -95,12 +139,12 @@ def __init__( if ( U == None or len(U) == 0 - ): # setting U #approx(X::Matrix{Float64}, y::Union{Vector{ComplexF64},Vector{Float64}}, ds::Int, N::Vector{Int}, basis::String = "cos"; classification::Bool = false, basis_vect::Vector{String} = Vector{String}([]), fastmult::Bool = classification ? true : false,) + ): # setting U #approx(X::Matrix{Float64}, y::Union{Vector{ComplexF64},Vector{Float64}}, ds::Int, N::Vector{Int}, basis::String = "cos"; classification::Bool = false, basis_vect::Vector{String} = Vector{String}([]), algorithm::String = classification ? true : false,) U = get_superposition_set(parent.X.shape[1], ds) if N is not None and not isinstance( N[0], tuple - ): # setting N #approx( X::Matrix{Float64}, y::Union{Vector{ComplexF64},Vector{Float64}}, U::Vector{Vector{Int}}, N::Vector{Int}, basis::String = "cos"; classification::Bool = false, basis_vect::Vector{String} = Vector{String}([]), fastmult::Bool = classification ? true : false,) + ): # setting N #approx( X::Matrix{Float64}, y::Union{Vector{ComplexF64},Vector{Float64}}, U::Vector{Vector{Int}}, N::Vector{Int}, basis::String = "cos"; classification::Bool = false, basis_vect::Vector{String} = Vector{String}([]), algorithm::String = classification ? true : false,) ds = max(len(u) for u in U) if len(N) != len(U) and len(N) != ds: @@ -126,8 +170,10 @@ def __init__( if basis_vect is None: basis_vect = [] - if fastmult is None: - fastmult = False if classification else True + if algorithm == "direct": + algorithm = "direct" if classification else "nfft" + if algorithm != "nfft": + parallel = False if basis not in bases: raise ValueError("Basis not found.") # if y[0].dtype != vtypes[basis]: @@ -146,32 +192,89 @@ def __init__( self.N = N self.classification = classification self.basis_vect = basis_vect - self.fastmult = fastmult + self.algorithm = algorithm + self.device = device self.parallel = parallel self.lam = lam self.parent = parent -### approx: -# A struct to hold the scattered data function approximation. - -## Fields -# * `basis::String` - basis of the function space; currently choice of `"per"` (exponential functions), `"cos"` (cosine functions), `"cheb"` (Chebyshev basis),`"std"`(transformed exponential functions), `"chui1"` (Haar wavelets), `"chui2"` (Chui-Wang wavelets of order 2),`"chui3"` (Chui-Wang wavelets of order 3) ,`"chui4"` (Chui-Wang wavelets of order 4) -# * `X::Matrix{Float64}` - scattered data nodes with d rows and M columns -# * `y::Union{Vector{ComplexF64},Vector{Float64}}` - M function values (complex for `basis = "per"`, real ortherwise) -# * `U::Vector{Vector{Int}}` - a vector containing susbets of coordinate indices -# * `N::Vector{Int}` - bandwdiths for each ANOVA term -# * `trafo::GroupedTransform` - holds the grouped transformation -# * `fc::Dict{Float64,GroupedCoefficients}` - holds the GroupedCoefficients after approximation for every different regularization parameters - -## Constructor -# approx( X::Matrix{Float64}, y::Union{Vector{ComplexF64},Vector{Float64}}, U::Vector{Vector{Int}}, N::Vector{Int}, basis::String = "cos" ) - -## Additional Constructor -# approx( X::Matrix{Float64}, y::Union{Vector{ComplexF64},Vector{Float64}}, ds::Int, N::Vector{Int}, basis::String = "cos" ) - - class approx: + r"""A scattered data function approximation. + + Holds an approximation built from scattered data nodes, function values, an + ANOVA decomposition and a choice of basis functions. Several bases and + several regularization parameters are supported. + + The ANOVA decomposition can be given **explicitly**, by passing the subsets + ``U`` and the bandwidths ``N``:: + + a = approx(X, y, U=U, N=N, basis="cos") + + or **automatically**, by passing the maximum interaction order ``ds``, in + which case ``U`` is generated as the superposition set of order ``ds``:: + + a = approx(X, y, ds=2, N=N, basis="cos") + + Args: + X (numpy.ndarray): Scattered data nodes, ``M`` rows and ``d`` columns. + The admissible range depends on ``basis``: ``[-0.5, 0.5)`` for + ``"per"`` and the ``"chui*"`` bases, ``[0, 1]`` for ``"cos"``, and + ``[-1, 1]`` for ``"cheb"``. + y (numpy.ndarray): ``M`` function values. Complex for ``basis="per"``, + real otherwise. + U (list[tuple[int]], optional): Subsets of coordinate indices defining + the ANOVA decomposition. If omitted or empty, it is generated from + ``ds``. + N (list, optional): Bandwidths. Either one entry per ANOVA term + (``|U|`` entries) or one per interaction order (``max |u|`` + entries). If ``None``, no transform is set up and you will want + :py:meth:`autoapproximate`. + basis (str): Basis of the function space. One of + + - ``"per"`` — periodic complex exponentials (Fourier basis) + - ``"cos"`` — cosine basis for non-periodic functions + - ``"cheb"`` — Chebyshev basis + - ``"std"`` — transformed exponential functions + - ``"chui1"`` — Haar wavelets + - ``"chui2"``, ``"chui3"``, ``"chui4"`` — Chui-Wang wavelets of + order 2, 3 and 4 + - ``"mixed"`` — per-dimension basis, see ``basis_vect`` + + Defaults to ``"cos"``. + classification (bool): Fit a classification rather than a regression + problem. Defaults to ``False``. + basis_vect (list[str]): Per-dimension basis choice, required for + ``basis="mixed"``. + algorithm (str, optional): Defaults to ``"nfft"``. ``"nfft"`` for the fast transforms, ``"keops"`` for implicit matrix multiplication (allow GPU or CPU computation), + or ``"direct"`` to build the matrix explicitly. + device: Device used by the ``"keops"`` algorithm, allows user to manually switch between GPU or CPU computation. Supported values are: ``"cuda"`` + for NVIDIA and CUDA-supporting devices, ``"mps"`` for Apple MPS-supporting devices, ``"cpu"`` for selecting CPU processing. + If ``None`` (default), the algorithm automatically selects the device based on availability, prioritizing GPU use. + parallel (bool): Run sub-transforms threaded. Defaults to ``True``; + ignored unless ``algorithm`` is ``"nfft"``. + ds (int, optional): Maximum interaction order, used to generate ``U`` + when it is not given explicitly. + lam (set[float]): Regularization parameters. Defaults to ``{0.0}``. + + Attributes: + X (numpy.ndarray): The scattered data nodes. + y (numpy.ndarray): The function values. + setting (list[approx_setting]): The ANOVA decompositions attached to + this approximation. + trafo (list): The grouped transform belonging to each setting. + fc (list[dict]): Per setting, a dictionary mapping each regularization + parameter to the :py:class:`GroupedCoefficients` obtained by + :py:meth:`approximate`. + aktsetting (int): Index of the currently active setting. + lam (dict[float, list[int]]): Maps each regularization parameter to the + settings that have been approximated for it. + + Raises: + ValueError: If ``X`` and ``y`` have different lengths, or if the nodes + in ``X`` lie outside the range required by ``basis``. + """ + def __init__( self, X, @@ -181,7 +284,8 @@ def __init__( basis="cos", classification=False, basis_vect=[], - fastmult=None, + algorithm="direct", + device=None, parallel=True, ds=None, lam={0.0}, @@ -204,7 +308,8 @@ def __init__( basis=basis, classification=classification, basis_vect=basis_vect, - fastmult=fastmult, + algorithm=algorithm, + device=device, parallel=parallel, ds=ds, lam=lam, @@ -270,7 +375,8 @@ def addTrafo(self, settingnr=None): U=setting.U, N=setting.N, X=transformX(self.X, setting.basis), - fastmult=setting.fastmult, + algorithm=setting.algorithm, + device=setting.device, parallel=setting.parallel, basis_vect=setting.basis_vect, ) @@ -368,9 +474,32 @@ def approximate( solver=None, tol=1e-8, ): - """ - If lam is a np.ndarray of dtype float, this function computes the approximation for the regularization parameters contained in lam. - If lam is a float, this function computes the approximation for the regularization parameter lam. + r"""Fit the approximation by solving a regularized least-squares problem. + + The coefficients are stored on the object and can afterwards be used by + :py:meth:`evaluate` and the analysis routines. + + Args: + lam (float or numpy.ndarray, optional): Regularization + parameter(s). A single ``float`` computes one approximation, an + array computes one per value. If omitted, the parameters + already stored on the setting are used. + settingnr (int, optional): Index of the setting to fit. Defaults to + the active setting. + max_iter (int): Maximum number of solver iterations. Defaults to 50. + weights (numpy.ndarray, optional): Weights for the regularization + term. + verbose (bool): Print solver progress. Defaults to ``False``. + solver (str, optional): Solver to use, e.g. ``"lsqr"`` or + ``"fista"``. If omitted, one is chosen from the setting. + tol (float): Solver tolerance. Defaults to ``1e-8``. + + Returns: + None: The coefficients are stored in :py:attr:`fc`. + + Raises: + ValueError: If the setting has no bandwidths ``N``. Use + :py:meth:`autoapproximate` in that case. """ setting = self.getSetting(settingnr) @@ -509,6 +638,36 @@ def autoapproximate( solver_verbose=False, solver_tol=1e-8, ): + r"""Fit the approximation, choosing the ANOVA decomposition automatically. + + Alternates between fitting coefficients and pruning the ANOVA terms, so + that the decomposition and the bandwidths do not have to be known in + advance. Each iteration may add a new setting; the settings found for + each regularization parameter are recorded in :py:attr:`lam`. + + Args: + lam (float or numpy.ndarray, optional): Regularization + parameter(s). If omitted, the parameters already stored on the + setting are used. + settingnr (int, optional): Index of the setting to start from. + Defaults to the active setting. + B (optional): Threshold controlling which ANOVA terms are retained + between iterations. + maxiter (int): Number of detection iterations. Defaults to 2. + solver (str): Solver used for the inner fits. Defaults to + ``"lsqr"``. + verbosity (int): Verbosity level of the detection loop. Defaults + to 0. + solver_max_iter (int): ``max_iter`` passed to the inner solver. + Defaults to 50. + solver_weights (numpy.ndarray, optional): ``weights`` passed to the + inner solver. + solver_verbose (bool): ``verbose`` passed to the inner solver. + solver_tol (float): ``tol`` passed to the inner solver. + + Returns: + None: The coefficients are stored in :py:attr:`fc`. + """ settingnr = self.getSettingNr(settingnr) setting = self.getSetting(settingnr) @@ -537,13 +696,26 @@ def autoapproximate( self.lam[l] = self.lam[l] + settingnrs def evaluate(self, settingnr=None, lam=None, X=None): - """ - This function evaluates the approximation with optional node matrix X and regularization lam. - - - If both X and lam are given: evaluate at X for specific lam. - - If only X is given: evaluate at X for all lam. - - If only lam is given: evaluate at self.X for specific lam. - - If neither are given: evaluate at self.X for all lam. + r"""Evaluate the fitted approximation. + + By default the approximation is evaluated at the nodes used during + construction. Optionally new evaluation points and a specific + regularization parameter can be given: + + - both ``X`` and ``lam``: evaluate at ``X`` for that ``lam`` + - only ``X``: evaluate at ``X`` for every ``lam`` + - only ``lam``: evaluate at :py:attr:`X` for that ``lam`` + - neither: evaluate at :py:attr:`X` for every ``lam`` + + Args: + settingnr (int, optional): Index of the setting to use. + lam (float, optional): Regularization parameter. + X (numpy.ndarray, optional): Evaluation nodes. Defaults to the + nodes the approximation was built from. + + Returns: + numpy.ndarray or dict: The evaluated function values. If ``lam`` is + omitted, a dictionary mapping each :math:`\lambda` to its values. """ setting = self.getSetting(settingnr, lam) @@ -573,11 +745,26 @@ def evaluate(self, settingnr=None, lam=None, X=None): return {λ: trafo @ self.getFc(settingnr, lam)[λ] for λ in self.lam.keys()} def evaluateANOVAterms(self, settingnr=None, X=None, lam=None): - """ - This function evaluates the single ANOVA terms of the approximation on the nodes of matrix X and regularization lam. - - - If lam is given: evaluate at X for specific lam. - - If lam is not given: evaluate at X for all lam. + r"""Evaluate the contribution of each ANOVA term separately. + + Instead of returning only the complete approximation, this returns the + value of every individual ANOVA component at the evaluation points. + + Args: + settingnr (int, optional): Index of the setting to use. + X (numpy.ndarray, optional): Evaluation nodes. Defaults to the + nodes the approximation was built from. + lam (float, optional): Regularization parameter. If omitted, all + computed approximations are evaluated. + + Returns: + numpy.ndarray or dict: An array with one column per ANOVA term. If + ``lam`` is omitted, a dictionary mapping each :math:`\lambda` to + such an array. + + Raises: + ValueError: If the nodes in ``X`` lie outside the range required by + the basis. """ setting = self.getSetting(settingnr, lam) if X is None: @@ -623,11 +810,27 @@ def evaluateANOVAterms(self, settingnr=None, X=None, lam=None): return results def evaluateSHAPterms(self, settingnr=None, X=None, lam=None): - """ - This function evaluates for each dimension the Shapley contribution to the overall approximation on the nodes of matrix X and regularization lam. - - - If lam is given: evaluate at X for specific lam. - - If lam is not given: evaluate at X for all lam. + r"""Evaluate the Shapley contribution of each input variable. + + The Shapley values quantify how much each variable contributes to the + predicted function value, while accounting for interactions between + variables. + + Args: + settingnr (int, optional): Index of the setting to use. + X (numpy.ndarray, optional): Evaluation nodes. Defaults to the + nodes the approximation was built from. + lam (float, optional): Regularization parameter. If omitted, all + computed approximations are evaluated. + + Returns: + numpy.ndarray or dict: An ``(M, d)`` array of per-variable + contributions. If ``lam`` is omitted, a dictionary mapping each + :math:`\lambda` to such an array. + + Raises: + ValueError: If the nodes in ``X`` lie outside the range required by + the basis. """ setting = self.getSetting(settingnr, lam)