#
# ************************************************************************
#
# ROM Tools and Workflows
# Copyright 2019 National Technology & Engineering Solutions of Sandia,LLC
# (NTESS)
#
# Under the terms of Contract DE-NA0003525 with NTESS, the
# U.S. Government retains certain rights in this software.
#
# ROM Tools and Workflows is licensed under BSD-3-Clause terms of use:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Questions? Contact Eric Parish (ejparis@sandia.gov)
#
# ************************************************************************
#
'''
Model reduction is often focused on parameterized PDEs, where
:math:`\\boldsymbol \\mu` is the parameter set.
The ParameterSpace class encapsulates the notion of the parameter space.
'''
import abc
from typing import Iterable
import numpy as np
from scipy.stats import norm
from romtools.workflows.sampling_methods import (
Sampler,
MonteCarloSampler,
RandomizedQuasiMonteCarloSampler,
)
from romtools.workflows.parameters import Parameter, StringParameter, UniformParameter, GaussianParameter
[docs]
class ParameterSpace(abc.ABC):
'''Abstract implementation'''
[docs]
@abc.abstractmethod
def get_names(self) -> Iterable[str]:
'''
Returns a list of parameter names
# e.g., ['sigma','beta',...]
'''
[docs]
@abc.abstractmethod
def get_dimensionality(self) -> int:
'''
Returns an integer for the size
of the parameter domain
'''
[docs]
@abc.abstractmethod
def generate_samples(self, number_of_samples: int, seed=None) -> np.array:
'''
Generates samples from the parameter space
Returns np.array of shape
(number_of_samples, self.get_dimensionality())
'''
[docs]
class BoundedParameterSpace(ParameterSpace):
'''Abstract implementation'''
[docs]
@abc.abstractmethod
def bound_samples(self, samples : np.ndarray) -> np.array:
'''
Inputs: a sample distribution that potentially exceeds
the bounds of the parameter space
Outputs:
a bounded sample distribution
(number_of_samples, self.get_dimensionality())
'''
##########################################
# Concrete ParameterSpace Classes
##########################################
[docs]
class HeterogeneousParameterSpace(ParameterSpace):
'''
Heterogeneous parameter space consisting of a list of arbitrary Parameter
objects
'''
def __init__(self, parameter_objs: Iterable[Parameter], sampler: Sampler = MonteCarloSampler):
self._parameters = parameter_objs
self._sampler = sampler
def _get_parameter_list(self) -> Iterable[Parameter]:
'''
Returns a list of Parameter objects
'''
return self._parameters
[docs]
def get_names(self) -> Iterable[str]:
return [p.get_name() for p in self._get_parameter_list()]
[docs]
def get_dimensionality(self) -> int:
return sum(p.get_dimensionality() for p in self._get_parameter_list())
[docs]
def generate_samples(self, number_of_samples: int, seed=None) -> np.array:
iid_samples = self._sampler(number_of_samples, self.get_dimensionality(), seed)
samples = []
param_idx = 0
for param in self._get_parameter_list():
next_param_idx = param_idx + param.get_dimensionality()
param_samples = param.scale_samples(iid_samples[:, param_idx:next_param_idx])
samples.append(param_samples)
param_idx = next_param_idx
return np.concatenate(samples, axis=1)
[docs]
class HomogeneousParameterSpace(HeterogeneousParameterSpace):
'''
Homogenous parameter space in which every parameter is of the same type
'''
def __init__(self, parameter_names: Iterable[str], sampler: Sampler, param_constructor, **kwargs):
parameters = []
for param_num, param_name in enumerate(parameter_names):
args = {key: val[param_num] for key, val in kwargs.items()}
parameters.append(param_constructor(parameter_name=param_name, **args))
super().__init__(parameters, sampler)
[docs]
class EmptyParameterSpace(ParameterSpace):
'''
Empty parameter space that is useful for initializations
'''
[docs]
def get_names(self) -> list:
return []
[docs]
def get_dimensionality(self) -> int:
return 0
[docs]
def generate_samples(self, number_of_samples: int, seed=None) -> np.ndarray:
return np.empty(shape=(number_of_samples, 0))
[docs]
class GaussianParameterSpace(HomogeneousParameterSpace):
'''
Homogeneous parameter space in which every parameter is a GaussianParameter
'''
def __init__(self, parameter_names: Iterable[str], means, stds, sampler: Sampler):
super().__init__(parameter_names, sampler=sampler, param_constructor=GaussianParameter, mean=means, std=stds)
[docs]
class MultivariateGaussianParameterSpace(ParameterSpace):
'''
Multivariate Gaussian parameter space with dense covariance coupling
across all parameter dimensions.
'''
def __init__(self, parameter_names: Iterable[str], means, covariance, sampler: Sampler = MonteCarloSampler):
self._parameter_names = list(parameter_names)
self._means = np.asarray(means)
self._covariance = np.asarray(covariance)
self._sampler = sampler
assert self._means.shape == (len(self._parameter_names),)
assert self._covariance.shape == (len(self._parameter_names), len(self._parameter_names))
self._cholesky_factor = np.linalg.cholesky(self._covariance)
[docs]
def get_names(self) -> Iterable[str]:
return self._parameter_names
[docs]
def get_dimensionality(self) -> int:
return len(self._parameter_names)
[docs]
def generate_samples(self, number_of_samples: int, seed=None) -> np.array:
iid_samples = self._sampler(number_of_samples, self.get_dimensionality(), seed)
std_normal_samples = norm.ppf(iid_samples)
return self._means + std_normal_samples @ self._cholesky_factor.T
[docs]
class ConstParameterSpace(HomogeneousParameterSpace):
'''
Homogeneous parameter space in which every parameter is a constant
StringParameter. All numeric values are converted to str-type.
Useful if you need to execute workflows in a non-stochastic setting
'''
def __init__(self, parameter_names: Iterable[str], parameter_values):
super().__init__(parameter_names, MonteCarloSampler, StringParameter, value=parameter_values)