Source code for gemseo.uncertainty.sensitivity.analysis

# 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.
# Contributors:
#    INITIAL AUTHORS - initial API and implementation and/or initial
#                           documentation
#        :author: Matthias De Lozzo
#    OTHER AUTHORS   - MACROSCOPIC CHANGES
"""Abstract class for the computation and analysis of sensitivity indices.

The purpose of a sensitivity analysis is to qualify or quantify how the model's
uncertain inputs impact its outputs.

This analysis relies on :class:`.BaseSensitivityAnalysis` computed from a
:class:`.MDODiscipline` representing the model,
a :class:`.ParameterSpace` describing the
uncertain parameters and options associated with a particular concrete class inheriting
from :class:`.BaseSensitivityAnalysis` which is an abstract one.
"""

from __future__ import annotations

import pickle
from abc import abstractmethod
from collections.abc import Collection
from collections.abc import Iterable
from collections.abc import Mapping
from collections.abc import Sequence
from copy import deepcopy
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from typing import Union

from numpy import array
from numpy import hstack
from numpy import linspace
from numpy import ndarray
from numpy import newaxis
from numpy import vstack
from pandas import MultiIndex

from gemseo.core.doe_scenario import DOEScenario
from gemseo.datasets.dataset import Dataset
from gemseo.disciplines.utils import get_all_outputs
from gemseo.post.dataset.bars import BarPlot
from gemseo.post.dataset.curves import Curves
from gemseo.post.dataset.radar_chart import RadarChart
from gemseo.post.dataset.surfaces import Surfaces
from gemseo.utils.constants import READ_ONLY_EMPTY_DICT
from gemseo.utils.file_path_manager import FilePathManager
from gemseo.utils.matplotlib_figure import save_show_figure
from gemseo.utils.metaclasses import ABCGoogleDocstringInheritanceMeta
from gemseo.utils.string_tools import repr_variable

if TYPE_CHECKING:
    from matplotlib.figure import Figure
    from numpy.typing import NDArray
    from strenum import StrEnum

    from gemseo.algos.doe.doe_library import DOELibraryOptionType
    from gemseo.algos.parameter_space import ParameterSpace
    from gemseo.core.discipline import MDODiscipline
    from gemseo.datasets.io_dataset import IODataset
    from gemseo.post.dataset.dataset_plot import DatasetPlot
    from gemseo.post.dataset.dataset_plot import DatasetPlotPropertyType
    from gemseo.post.dataset.dataset_plot import VariableType

OutputsType = Union[str, tuple[str, int], Sequence[Union[str, tuple[str, int]]]]
FirstOrderIndicesType = dict[str, list[dict[str, ndarray]]]
SecondOrderIndicesType = dict[str, list[dict[str, dict[str, ndarray]]]]


[docs] class BaseSensitivityAnalysis(metaclass=ABCGoogleDocstringInheritanceMeta): """Sensitivity analysis. The :class:`.BaseSensitivityAnalysis` class provides both the values of :attr:`.BaseSensitivityAnalysis.indices` and their graphical representations, from either the :meth:`.plot` method, the :meth:`.plot_radar` method or the :meth:`.plot_bar` method. It is also possible to use :meth:`.sort_parameters` to get the parameters sorted according to :attr:`.main_method`. The :attr:`.main_indices` are indices computed with the latter. Lastly, the :meth:`.plot_comparison` method allows to compare the current :class:`.BaseSensitivityAnalysis` with another one. """ # TODO: API: rename to default_outputs or default_output_names default_output: Iterable[str] """The default outputs of interest.""" dataset: IODataset """The dataset containing the discipline evaluations.""" Method: ClassVar[type[StrEnum]] """The names of the sensitivity methods considering simple effects. A simple effect is the effect of an isolated input variable on an output variable while an interaction effect is the effect of the interaction between several input variables on an output variable. """ _INTERACTION_METHODS: ClassVar[tuple[str]] = () """The names of the sensitivity methods considering interaction effects.""" DEFAULT_DRIVER = None _input_names: list[str] """The names of the inputs in parameter space order.""" _output_names: Iterable[str] """The disciplines' outputs to be considered for the analysis.""" _algo_name: str """The name of the DOE algorithm to sample the discipline.""" _file_path_manager: FilePathManager """The file path manager for the figures.""" _main_method: Method # noqa: F821 """The name of the main sensitivity analysis method.""" _indices: dict[str, FirstOrderIndicesType] """The sensitivity indices computed by the method compute_indices. With the following structure: .. code-block:: python { "method_name": { "output_name": [ { "input_name": data_array, } ] } } """ def __init__( self, disciplines: Collection[MDODiscipline], parameter_space: ParameterSpace, n_samples: int | None = None, output_names: Iterable[str] = (), algo: str = "", algo_options: Mapping[str, DOELibraryOptionType] = READ_ONLY_EMPTY_DICT, formulation: str = "MDF", **formulation_options: Any, ) -> None: """ Args: disciplines: The discipline or disciplines to use for the analysis. parameter_space: A parameter space. n_samples: A number of samples. If ``None``, the number of samples is computed by the algorithm. output_names: The disciplines' outputs to be considered for the analysis. If empty, use all the outputs. algo: The name of the DOE algorithm. If empty, use the :attr:`.BaseSensitivityAnalysis.DEFAULT_DRIVER`. algo_options: The options of the DOE algorithm. formulation: The name of the :class:`.MDOFormulation` to sample the disciplines. **formulation_options: The options of the :class:`.MDOFormulation`. """ # noqa: D205, D212, D415 disciplines = list(disciplines) self._algo_name = algo or self.DEFAULT_DRIVER self._output_names = output_names or get_all_outputs(disciplines) self.default_output = self._output_names self._input_names = parameter_space.variable_names self.dataset = self.__sample_disciplines( disciplines, parameter_space, n_samples, algo_options, formulation, **(formulation_options or {}), ).to_dataset(opt_naming=False) self._main_method = None self._file_path_manager = FilePathManager( FilePathManager.FileType.FIGURE, default_name=FilePathManager.to_snake_case(self.__class__.__name__), )
[docs] def to_pickle(self, file_path: str | Path) -> None: """Save the current sensitivity analysis on the disk. Args: file_path: The path to the file. """ with Path(file_path).open("wb") as f: pickle.dump(self, f)
[docs] @staticmethod def from_pickle(file_path: str | Path) -> BaseSensitivityAnalysis: """Load a sensitivity analysis from the disk. Args: file_path: The path to the file. Returns: The sensitivity analysis. """ with Path(file_path).open("rb") as f: return pickle.load(f)
def __sample_disciplines( self, disciplines: Sequence[MDODiscipline], parameter_space: ParameterSpace, n_samples: int | None, algo_options: Mapping[str, DOELibraryOptionType], formulation: str, **formulation_options: Any, ) -> DOEScenario: """Sample the disciplines and return the scenario after evaluation. Args: disciplines: The disciplines to sample. parameter_space: A parameter space. n_samples: A number of samples. If ``None``, the number of samples is computed by the algorithm. algo_options: The options for the DOE algorithm. formulation: The name of the :class:`.MDOFormulation` to sample the disciplines. **formulation_options: The options of the :class:`.MDOFormulation`. Returns: The DOE scenario after evaluation. """ scenario = self._create_scenario( disciplines, self._output_names, formulation, formulation_options, parameter_space, ) algo_options = algo_options or {} algo_options["log_problem"] = False algo_options["use_one_line_progress_bar"] = True scenario.execute({ scenario.ALGO: self._algo_name, scenario.N_SAMPLES: n_samples, scenario.ALGO_OPTIONS: algo_options, }) return scenario def _create_scenario( self, disciplines: Iterable[MDODiscipline], observable_names: Sequence[str], formulation: str, formulation_options: Mapping[str, Any], parameter_space: ParameterSpace, ) -> DOEScenario: """Create a DOE scenario to sample the disciplines. Args: disciplines: The disciplines to sample. observable_names: The names of the observables. formulation: The name of the :class:`.MDOFormulation` to sample the disciplines. formulation_options: The options of the :class:`.MDOFormulation`. parameter_space: A parameter space. Returns: The DOE scenario to be used to sample the disciplines. """ scenario = DOEScenario( disciplines, formulation, observable_names[0], parameter_space, name=f"{self.__class__.__name__}SamplingPhase", **formulation_options, ) for discipline in disciplines: for output_name in discipline.get_output_data_names(): if output_name in observable_names[1:]: scenario.add_observable(output_name) return scenario @property def input_names(self) -> list[str]: """The names of the inputs.""" return self._input_names
[docs] @abstractmethod def compute_indices( self, outputs: str | Sequence[str] = () ) -> dict[str, FirstOrderIndicesType | SecondOrderIndicesType]: """Compute the sensitivity indices. Args: outputs: The name(s) of the output(s) for which to compute the sensitivity indices. If empty, use the names of the outputs set at instantiation. Returns: The sensitivity indices. With the following structure: .. code-block:: python { "method_name": { "output_name": [ { "input_name": data_array, } ] } } """
@property def indices(self) -> dict[str, FirstOrderIndicesType]: """The sensitivity indices. With the following structure: .. code-block:: python { "method_name": { "output_name": [ { "input_name": data_array, } ] } } """ return self._indices @property def main_method(self) -> Method: # noqa: F821 """The name of the main method. One of the enum :class:`.Sensitivity.Method`. """ return self._main_method @main_method.setter def main_method(self, method: Method) -> None: # noqa: D102, F821 self._main_method = method @property def main_indices(self) -> FirstOrderIndicesType: """The main sensitivity indices. With the following structure: .. code-block:: python { "output_name": [ { "input_name": data_array, } ] } """ return self.indices[self._main_method] def _outputs_to_tuples( self, outputs: OutputsType, ) -> list[tuple[str, int]]: """Convert the outputs to a list of tuple(str,int). Args: outputs: The outputs for which to display sensitivity indices, either a name, a list of names, a (name, component) tuple, a list of such tuples or a list mixing such tuples and names. When a name is specified, all its components are considered. If ``None``, use the default outputs. Returns: The outputs. The outputs are formatted as tuples of the form (name, component), where name is the output name and component is the output component. """ if not isinstance(outputs, list): outputs = [outputs] def get_all(output): return [(output, index) for index in range(len(self.main_indices[output]))] result = [ [output] if isinstance(output, tuple) else get_all(output) for output in outputs ] return [item for sublist in result for item in sublist]
[docs] def sort_parameters(self, output: VariableType) -> list[str]: """Return the parameters sorted in descending order. Args: output: Either a tuple as ``(output_name, output_component)`` or an output name; in the second case, use the first output component. Returns: The input parameters sorted by decreasing order of sensitivity; in case of a multivariate input, aggregate the sensitivity indices associated to the different input components by adding them up typically. """ if isinstance(output, str): output_name, output_index = output, 0 else: output_name, output_index = output return [ input_name for input_name, _ in sorted( self.main_indices[output_name][output_index].items(), key=lambda item: self._aggregate_sensitivity_indices(item[1]), reverse=True, ) ]
@staticmethod def _aggregate_sensitivity_indices(indices: NDArray[float]) -> float: """Aggregate sensitivity indices. Args: indices: The sensitivity indices to be aggregated. Returns: The aggregated index. """ return indices.sum()
[docs] def plot( self, output: VariableType, inputs: Iterable[str] = (), title: str = "", save: bool = True, show: bool = False, file_path: str | Path = "", file_format: str = "", ) -> DatasetPlot | Figure: """Plot the sensitivity indices. Args: output: The output for which to display sensitivity indices, either a name or a tuple of the form (name, component). If name, its first component is considered. inputs: The uncertain input variables for which to display the sensitivity indices. If empty, display all the uncertain input variables. title: The title of the plot, if any. save: If ``True``, save the figure. show: If ``True``, show the figure. file_path: A file path. Either a complete file path, a directory name or a file name. If empty, use a default file name and a default directory. The file extension is inferred from filepath extension, if any. file_format: A file format, e.g. 'png', 'pdf', 'svg', ... Used when ``file_path`` does not have any extension. If empty, use a default file extension. Returns: The plot figure. """ raise NotImplementedError
[docs] def plot_field( self, output: VariableType, mesh: ndarray | None = None, inputs: Iterable[str] = (), standardize: bool = False, title: str = "", save: bool = True, show: bool = False, file_path: str | Path = "", directory_path: str | Path = "", file_name: str = "", file_format: str = "", properties: Mapping[str, DatasetPlotPropertyType] = READ_ONLY_EMPTY_DICT, ) -> Curves | Surfaces: """Plot the sensitivity indices related to a 1D or 2D functional output. The output is considered as a 1D or 2D functional variable, according to the shape of the mesh on which it is represented. Args: output: The output for which to display sensitivity indices, either a name or a tuple of the form (name, component) where (name, component) is used to sort the inputs. If it is a name, its first component is considered. mesh: The mesh on which the p-length output is represented. Either a p-length array for a 1D functional output or a (p, 2) array for a 2D one. If ``None``, assume a 1D functional output. inputs: The uncertain input variables for which to display the sensitivity indices. If empty, display all the uncertain input variables. standardize: Whether to scale the indices to :math:`[0,1]`. title: The title of the plot, if any. save: If ``True``, save the figure. show: If ``True``, show the figure. file_path: The path of the file to save the figures. If empty, create a file path from ``directory_path``, ``file_name`` and ``file_extension``. directory_path: The path of the directory to save the figures. If empty, use the current working directory. file_name: The name of the file to save the figures. If empty, use a default one generated by the post-processing. file_format: A file extension, e.g. 'png', 'pdf', 'svg', ... If empty, use a default file extension. properties: The general properties of a :class:`.DatasetPlot`. Returns: A bar plot representing the sensitivity indices. Raises: NotImplementedError: If the dimension of the mesh is greater than 2. """ if isinstance(output, str): output_name = output output_component = 0 else: output_name, output_component = output input_names = self._sort_and_filter_input_parameters( (output_name, output_component), inputs ) if standardize: main_indices = self.standardize_indices(self.main_indices) else: main_indices = self.main_indices data = [ [main_index[input_name] for main_index in main_indices[output_name]] for input_name in input_names ] data = array(data)[:, :, 0] dataset = Dataset.from_array(data, [output_name], {output_name: data.shape[1]}) dataset.index = input_names mesh = linspace(0, 1, data.shape[1]) if mesh is None else mesh dataset.misc["mesh"] = mesh mesh_dimension = len(dataset.misc["mesh"].shape) if mesh_dimension == 1: plot = Curves(dataset, mesh="mesh", variable=output_name) elif mesh_dimension == 2: plot = Surfaces(dataset, mesh="mesh", variable=output_name) else: raise NotImplementedError for k, v in properties.items(): setattr(plot, k, v) plot.title = title plot.execute( save=save, show=show, file_path=file_path, file_name=file_name, file_format=file_format, directory_path=directory_path, ) return plot
[docs] def plot_bar( self, outputs: OutputsType = (), inputs: Iterable[str] = (), standardize: bool = False, title: str = "", save: bool = True, show: bool = False, file_path: str | Path = "", directory_path: str | Path = "", file_name: str = "", file_format: str = "", sort: bool = True, sorting_output: VariableType = "", **options: int, ) -> BarPlot: """Plot the sensitivity indices on a bar chart. This method may consider one or more outputs, as well as all inputs (default behavior) or a subset. Args: outputs: The outputs for which to display sensitivity indices, either a name, a list of names, a (name, component) tuple, a list of such tuples or a list mixing such tuples and names. When a name is specified, all its components are considered. If empty, use the default outputs. inputs: The uncertain input variables for which to display the sensitivity indices. If empty, display all the uncertain input variables. standardize: Whether to scale the indices to :math:`[0,1]`. title: The title of the plot, if any. save: If ``True``, save the figure. show: If ``True``, show the figure. file_path: The path of the file to save the figures. If the extension is missing, use ``file_extension``. If empty, create a file path from ``directory_path``, ``file_name`` and ``file_extension``. directory_path: The path of the directory to save the figures. If empty, use the current working directory. file_name: The name of the file to save the figures. If empty, use a default one generated by the post-processing. file_format: A file extension, e.g. 'png', 'pdf', 'svg', ... If None, use a default file extension. **options: The options to instantiate the :class:`.BarPlot`. If empty, use a default file extension. sort: Whether to sort the uncertain variables by decreasing order of the sensitivity indices associated with the sorting output variable. sorting_output: The sorting output variable If empty, use the first one. **options: The options to instantiate the :class:`.BarPlot`. Returns: A bar chart representing the sensitivity indices. """ outputs = outputs or self._output_names _options = {"n_digits": 2} _options.update(options) plot = BarPlot( self.__create_dataset_to_plot( inputs, outputs, standardize, sort, sorting_output ), **_options, ) plot.title = title plot.execute( save=save, show=show, file_path=file_path, file_name=file_name, file_format=file_format, directory_path=directory_path, ) return plot
def __create_dataset_to_plot( self, inputs: Iterable[str], outputs: OutputsType, standardize: bool, sort: True, sorting_output: VariableType, ) -> Dataset: r"""Create the dataset to plot. Args: inputs: The uncertain input variables for which to display the sensitivity indices. If empty, display all the uncertain input variables. outputs: The outputs for which to display sensitivity indices, either a name, a list of names, a (name, component) tuple, a list of such tuples or a list mixing such tuples and names. When a name is specified, all its components are considered. standardize: Whether to scale the indices to :math:`[0,1]`. sort: Whether to sort the uncertain variables by decreasing order of the sensitivity indices associated with the sorting output variable. sorting_output: The sorting output variable If empty, use the first one. Returns: The dataset to plot. """ outputs = self._outputs_to_tuples(outputs) if standardize: main_indices = self.standardize_indices(self.main_indices) else: main_indices = self.main_indices input_names = self._sort_and_filter_input_parameters(outputs[0], inputs) data = {name: [] for name in input_names} for output_name, output_component in outputs: indices = main_indices[output_name][output_component] for input_name in input_names: data[input_name].append(indices[input_name]) dataset = Dataset( hstack([vstack(data[input_name]) for input_name in input_names]), columns=MultiIndex.from_tuples( [ (Dataset.PARAMETER_GROUP, input_name, index) for input_name in input_names for index in range( self.dataset.variable_names_to_n_components[input_name] ) ], names=Dataset.COLUMN_LEVEL_NAMES, ), ) dataset.index = [ repr_variable( *output, size=self.dataset.variable_names_to_n_components[output[0]] ) for output in outputs ] if sort: if sorting_output: if isinstance(sorting_output, str): sorting_output = (sorting_output, 0) sorting_output = self._outputs_to_tuples([sorting_output])[0] by = repr_variable( *sorting_output, size=self.dataset.variable_names_to_n_components[sorting_output[0]], ) else: by = dataset.index[0] dataset = dataset.sort_values(by=by, ascending=False, axis=1) return dataset
[docs] def plot_radar( self, outputs: OutputsType = (), inputs: Iterable[str] = (), standardize: bool = False, title: str = "", save: bool = True, show: bool = False, file_path: str | Path = "", directory_path: str | Path = "", file_name: str = "", file_format: str = "", min_radius: float | None = None, max_radius: float | None = None, sort: bool = True, sorting_output: VariableType = "", **options: bool | int, ) -> RadarChart: """Plot the sensitivity indices on a radar chart. This method may consider one or more outputs, as well as all inputs (default behavior) or a subset. For visualization purposes, it is also possible to change the minimum and maximum radius values. Args: outputs: The outputs for which to display sensitivity indices, either a name, a list of names, a (name, component) tuple, a list of such tuples or a list mixing such tuples and names. When a name is specified, all its components are considered. If empty, use the default outputs. inputs: The uncertain input variables for which to display the sensitivity indices. If empty, display all the uncertain input variables. standardize: Whether to scale the indices to :math:`[0,1]`. title: The title of the plot, if any. save: If ``True``, save the figure. show: If ``True``, show the figure. file_path: The path of the file to save the figures. If the extension is missing, use ``file_extension``. If empty, create a file path from ``directory_path``, ``file_name`` and ``file_extension``. directory_path: The path of the directory to save the figures. If empty, use the current working directory. file_name: The name of the file to save the figures. If empty, use a default one generated by the post-processing. file_format: A file extension, e.g. 'png', 'pdf', 'svg', ... If empty, use a default file extension. min_radius: The minimal radial value. If ``None``, from data. max_radius: The maximal radial value. If ``None``, from data. sort: Whether to sort the uncertain variables by decreasing order of the sensitivity indices associated with the sorting output variable. sorting_output: The sorting output variable If empty, use the first one. **options: The options to instantiate the :class:`.RadarChart`. Returns: A radar chart representing the sensitivity indices. """ outputs = outputs or self._output_names plot = RadarChart( self.__create_dataset_to_plot( inputs, outputs, standardize, sort, sorting_output ), **options, ) plot.title = title plot.rmin = min_radius or plot.rmin plot.rmax = max_radius or plot.rmax plot.execute( save=save, show=show, file_path=file_path, file_name=file_name, file_format=file_format, directory_path=directory_path, ) return plot
@staticmethod def _filter_names( names: Iterable[str], names_to_keep: Iterable[str], ) -> list[str]: """Sort and filter the names. Args: names: The original names. names_to_keep: The names to keep. If ``None``, keep all. Returns: The filtered names. """ if names_to_keep: names = [item for item in names if item in set(names_to_keep)] return names def _sort_and_filter_input_parameters( self, output: tuple[str, int], inputs_to_keep: Iterable[str] ) -> list[str]: """Sort and filter the input parameters. Args: output: An output for which to display sensitivity indices. inputs_to_keep: The inputs to keep. If ``None``, keep all. Returns: The filtered names. """ return self._filter_names(self.sort_parameters(output), inputs_to_keep)
[docs] def plot_comparison( self, indices: list[BaseSensitivityAnalysis], output: VariableType, inputs: Iterable[str] = (), title: str = "", use_bar_plot: bool = True, save: bool = True, show: bool = False, file_path: str | Path = "", directory_path: str | Path = "", file_name: str = "", file_format: str = "", **options: bool, ) -> BarPlot | RadarChart: """Plot a comparison between the current sensitivity indices and other ones. This method allows to use either a bar chart (default option) or a radar one. Args: indices: The sensitivity indices. output: The output for which to display sensitivity indices, either a name or a tuple of the form (name, component). If name, its first component is considered. inputs: The uncertain input variables for which to display the sensitivity indices. If empty, display all the uncertain input variables. title: The title of the plot, if any. use_bar_plot: The type of graph. If ``True``, use a bar plot. Otherwise, use a radar chart. save: If ``True``, save the figure. show: If ``True``, show the figure. file_path: The path of the file to save the figures. If empty, create a file path from ``directory_path``, ``file_name`` and ``file_format``. directory_path: The path of the directory to save the figures. If empty, use the current working directory. file_name: The name of the file to save the figures. If empty, use a default one generated by the post-processing. file_format: A file format, e.g. 'png', 'pdf', 'svg', ... If empty, use a default file extension. **options: The options passed to the underlying :class:`.DatasetPlot`. Returns: A graph comparing sensitivity indices. """ if not isinstance(output, tuple): output = (output, 0) if isinstance(indices, BaseSensitivityAnalysis): indices = [indices] methods = [self, *indices] dataset = Dataset() input_names = self._sort_and_filter_input_parameters(output, inputs) for name in input_names: data = abs( array([ method.main_indices[output[0]][output[1]][name] for method in methods ]) ) dataset.add_variable(name, data) data = dataset.get_view(group_names=dataset.PARAMETER_GROUP).to_numpy() dataset.update_data( data / data.max(axis=1)[:, newaxis], group_names=dataset.PARAMETER_GROUP ) dataset.index = [method.main_method for method in methods] if use_bar_plot: plot = BarPlot(dataset, n_digits=2) else: plot = RadarChart(dataset) plot.rmin = 0.0 plot.rmax = 1.0 plot.title = title plot.execute( save, show, file_path, directory_path, file_name, file_format, **options ) return plot
def _save_show_plot( self, fig: Figure, save: bool = True, show: bool = False, file_path: str | Path = "", directory_path: str | Path = "", file_name: str = "", file_format: str = "", ) -> Figure: """Save or show the plot. Args: fig: The figure to be processed. save: If ``True``, save the figure. show: If ``True``, show the figure. file_path: The path of the file to save the figures. If empty, create a file path from ``directory_path``, ``file_name`` and ``file_format``. directory_path: The path of the directory to save the figures. If empty, use the current working directory. file_name: The name of the file to save the figures. If empty, use a default one generated by the post-processing. file_format: A file format, e.g. 'png', 'pdf', 'svg', ... If empty, use a default file extension. Returns: The figure. """ if save: file_path = self._file_path_manager.create_file_path( file_path=file_path, directory_path=directory_path, file_name=file_name, file_extension=file_format, ) else: file_path = "" save_show_figure(fig, show, file_path) return fig
[docs] def to_dataset(self) -> Dataset: """Convert :attr:`.BaseSensitivityAnalysis.indices` into a :class:`.Dataset`. Returns: The sensitivity indices. """ sizes = self.dataset.variable_names_to_n_components row_names = [] for input_name in self.input_names: for input_component in range(sizes[input_name]): row_names.append( # noqa: PERF401 repr_variable( input_name, input_component, size=self.dataset.variable_names_to_n_components[input_name], ) ) dataset = Dataset() for method, indices in self.indices.items(): if method in self._INTERACTION_METHODS: dataset.misc[method] = indices continue variables = [] sizes = {} data = [] for output, components in indices.items(): variables.append(output) sizes[output] = len(components) for component in components: data.append([component[name].tolist() for name in self.input_names]) data[-1] = [item for sublist in data[-1] for item in sublist] data = array(data).T dataset.add_group( method, data, [f"{v}" for v in variables], {f"{v}": s for v, s in sizes.items()}, ) dataset.index = row_names return dataset
[docs] @staticmethod def standardize_indices( indices: FirstOrderIndicesType, ) -> FirstOrderIndicesType: """Standardize the sensitivity indices for each output component. Each index is replaced by its absolute value divided by the largest index. Thus, the standardized indices belong to the interval :math:`[0,1]`. Args: indices: The indices to be standardized. Returns: The standardized indices. """ new_indices = deepcopy(indices) for output_name, output_indices in indices.items(): for output_component, output_component_indices in enumerate(output_indices): max_value = max( abs(value)[0] for value in output_component_indices.values() ) for input_name, input_indices in output_component_indices.items(): new_indices[output_name][output_component][input_name] = ( abs(input_indices) / max_value ) return new_indices