#! /usr/bin/env python
"""Abstract base classes for Dakota analysis methods."""
from abc import ABCMeta, abstractmethod
[docs]class MethodBase(object):
"""Describe common features of Dakota analysis methods.
The *max_iterations* and *convergence_tolerance* keywords are
included in Dakota's set of `method independent controls`_.
.. _method independent controls:
https://dakota.sandia.gov//sites/default/files/docs/6.4/html-ref/topic-method_independent_controls.html
"""
__metaclass__ = ABCMeta
[docs] @abstractmethod
def __init__(
self,
method="vector_parameter_study",
max_iterations=None,
convergence_tolerance=None,
**kwargs
):
"""Create default method parameters.
Parameters
----------
method : str
The name of the analysis method; e.g., 'vector_parameter_study'.
max_iterations : int, optional
Stopping criterion based on number of iterations.
convergence_tolerance : float, optional
Stopping criterion based on convergence of the objective
function or statistics. Defined on the open interval (0, 1).
"""
self._method = method
self._max_iterations = max_iterations
self._convergence_tolerance = convergence_tolerance
@property
def method(self):
"""The name of the analysis method used in the experiment."""
return self._method
@method.setter
def method(self, value):
"""Set the analysis method used in the experiment.
Parameters
----------
value : str
The new method type.
"""
if not isinstance(value, str):
raise TypeError("Method must be a str")
self._method = value
@property
def max_iterations(self):
"""Maximum number of iterations for the method."""
return self._max_iterations
@max_iterations.setter
def max_iterations(self, value):
"""Set the maximum number of iterations used in the experiment.
Parameters
----------
value : int
The maximum number of iterations.
"""
if not isinstance(value, int):
raise TypeError("Max iterations must be a int")
self._max_iterations = value
@property
def convergence_tolerance(self):
"""Convergence tolerance for the method."""
return self._convergence_tolerance
@convergence_tolerance.setter
def convergence_tolerance(self, value):
"""Set the convergence tolerance used in the experiment.
Parameters
----------
value : float
The new convergence tolerance.
"""
if not isinstance(value, float):
raise TypeError("Convergence tolerance must be a float")
if value <= 0.0 or value >= 1.0:
raise ValueError("Convergence tolerance must be on (0,1)")
self._convergence_tolerance = value
[docs] def __str__(self):
"""Define the preamble of the Dakota input file method block."""
s = "method\n" + " {}\n".format(self.method)
if self.max_iterations is not None:
s += " max_iterations = "
s += "{}\n".format(self.max_iterations)
if self.convergence_tolerance is not None:
s += " convergence_tolerance = "
s += "{}\n".format(self.convergence_tolerance)
return s
def _print_levels(levels):
s = ""
for item in levels:
if isinstance(item, (tuple, list)):
s += "\n "
for subitem in item:
s += " {}".format(subitem)
else:
s += " {}".format(item)
s += "\n"
return s
[docs]class UncertaintyQuantificationBase(MethodBase):
"""Describe features of uncertainty quantification methods.
To supply *probability_levels* or *response_levels* to multiple
responses, nest the inputs to these properties.
"""
__metaclass__ = ABCMeta
[docs] @abstractmethod
def __init__(
self,
basis_polynomial_family="extended",
probability_levels=(0.1, 0.5, 0.9),
response_levels=(),
samples=10,
sample_type="random",
seed=None,
variance_based_decomp=False,
**kwargs
):
"""Create default method parameters.
Parameters
----------
basis_polynomial_family: str, optional
The type of polynomial basis used in the expansion, either
'extended' (the default), 'askey', or 'wiener'.
probability_levels : list or tuple of float, optional
Specify probability levels at which to estimate the
corresponding response value. Default is (0.1, 0.5, 0.9).
response_levels : list or tuple of float, optional
Values at which to estimate desired statistics for each response
samples : int
The number of randomly chosen values at which to execute a model.
sample_type : str
Technique for choosing samples, `random` or `lhs`.
seed : int, optional
The seed for the random number generator. If seed is
specified, a stochastic study will generate identical
results when repeated using the same seed value. If not
specified, a seed is randomly generated.
variance_based_decomp : bool, optional
Set to activate global sensitivity analysis based on
decomposition of response variance into main, interaction,
and total effects.
"""
MethodBase.__init__(self, method="sampling", **kwargs)
self._basis_polynomial_family = basis_polynomial_family
self._probability_levels = probability_levels
self._response_levels = response_levels
self._samples = samples
self._sample_type = sample_type
self._seed = seed
self._variance_based_decomp = variance_based_decomp
@property
def basis_polynomial_family(self):
"""The type of basis polynomials used by the method."""
return self._basis_polynomial_family
@basis_polynomial_family.setter
def basis_polynomial_family(self, value):
"""Set the type of basis polynomials used by the method.
Parameters
----------
value : str
The polynomial type.
"""
if value not in ("extended", "askey", "wiener"):
msg = "Polynomial type must be 'extended', 'askey', or 'wiener'"
raise TypeError(msg)
self._basis_polynomial_family = value
@property
def probability_levels(self):
"""Probabilities at which to estimate response values."""
return self._probability_levels
@probability_levels.setter
def probability_levels(self, value):
"""Set probabilities at which to estimate response values.
Parameters
----------
value : tuple or list of float
The probability levels.
"""
if not isinstance(value, (tuple, list)):
raise TypeError("Probability levels must be a tuple or a list")
self._probability_levels = value
@property
def response_levels(self):
"""Values at which to estimate statistics for responses."""
return self._response_levels
@response_levels.setter
def response_levels(self, value):
"""Set values at which to estimate statistics for responses.
Parameters
----------
value : tuple or list of float
The response levels.
"""
if not isinstance(value, (tuple, list)):
raise TypeError("Response levels must be a tuple or a list")
self._response_levels = value
@property
def samples(self):
"""Number of samples in experiment."""
return self._samples
@samples.setter
def samples(self, value):
"""Set number of samples used in experiment.
Parameters
----------
value : int
The number of samples.
"""
if type(value) is not int:
raise TypeError("Samples must be an int")
self._samples = value
@property
def sample_type(self):
"""Sampling strategy."""
return self._sample_type
@sample_type.setter
def sample_type(self, value):
"""Set sampling strategy used in experiment.
Parameters
----------
value : str
The sampling strategy.
"""
if ["random", "lhs"].count(value) == 0:
raise TypeError("Sample type must be 'random' or 'lhs'")
self._sample_type = value
@property
def seed(self):
"""Seed of the random number generator."""
return self._seed
@seed.setter
def seed(self, value):
"""Set the seed of the random number generator.
Parameters
----------
value : int
The random number generator seed.
"""
if type(value) is not int:
raise TypeError("Seed must be an int")
self._seed = value
@property
def variance_based_decomp(self):
"""Use variance-based decomposition global sensitivity analysis."""
return self._variance_based_decomp
@variance_based_decomp.setter
def variance_based_decomp(self, value):
"""Toggle variance-based decomposition global sensitivity analysis.
Parameters
----------
value : bool
True if using variance-based decomposition.
"""
if type(value) is not bool:
raise TypeError("Set variance-based decomposition with a bool")
self._variance_based_decomp = value
[docs] def __str__(self):
"""Define the method block for a UQ experiment.
See Also
--------
dakotathon.method.base.MethodBase.__str__
"""
s = MethodBase.__str__(self)
if self.basis_polynomial_family != "extended":
s += " {}\n".format(self.basis_polynomial_family)
s += " sample_type = {}\n".format(
self.sample_type
) + " samples = {}\n".format(self.samples)
if self.seed is not None:
if self.seed != 0:
s += " seed = {}\n".format(self.seed)
if len(self.probability_levels) > 0:
s += " probability_levels ="
s += _print_levels(self.probability_levels)
if len(self.response_levels) > 0:
s += " response_levels ="
s += _print_levels(self.response_levels)
if self.variance_based_decomp:
s += " variance_based_decomp\n"
return s