Skip to content

Commit

Permalink
Merge pull request #11 from MSDLLCpapers/release_docs
Browse files Browse the repository at this point in the history
Release docs
  • Loading branch information
kstone40 authored Aug 9, 2024
2 parents c573cca + a43aaa9 commit bd0e7d7
Show file tree
Hide file tree
Showing 32 changed files with 309 additions and 110 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [0.7.12]
### Added
- More informative docstrings for optimizer.bayesian, optimizer.predict, to explain choices of surrogate models and aq_funcs

### Modified
- Renamed aq_func hyperparmeter "Xi_f" to "inflate"
- Moved default aq_func choices for single/multi into aq_defaults of acquisition.config

## [0.7.11]
### Added
- Documentation improvements
Expand Down
2 changes: 1 addition & 1 deletion obsidian/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""obsidian: Automated experiment design and black-box optimization"""
__version__ = '0.7.11'
__version__ = '0.7.12'

from obsidian.campaign import Campaign
from obsidian.optimizer import BayesianOptimizer
Expand Down
5 changes: 3 additions & 2 deletions obsidian/acquisition/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
universal_aqs = ['RS', 'Mean', 'SF']
valid_aqs = {'single': ['EI', 'NEI', 'PI', 'UCB', 'SR', 'NIPV'] + universal_aqs,
'multi': ['EHVI', 'NEHVI', 'NParEGO'] + universal_aqs}
aq_defaults = {'single': 'NEI', 'multi': 'NEHVI'}


# For default values, 'optional' indicates whether or not a default value (or None) 'val' can be used
aq_hp_defaults = {
'EI': {'Xi_f': {'val': 0, 'optional': True}},
'EI': {'inflate': {'val': 0, 'optional': True}},
'NEI': {},
'PI': {'Xi_f': {'val': 0, 'optional': True}},
'PI': {'inflate': {'val': 0, 'optional': True}},
'UCB': {'beta': {'val': 1, 'optional': True}},
'SR': {},
'RS': {},
Expand Down
6 changes: 6 additions & 0 deletions obsidian/acquisition/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ def __init__(self,
@t_batch_mode_transform()
def forward(self,
x: Tensor) -> Tensor:
"""
Evaluate the acquisition function on the candidate set x
"""
# x dimensions: b * q * d
posterior = self.model.posterior(x) # dimensions: b * q
samples = self.get_posterior_samples(posterior) # dimensions: n * b * q * o
Expand Down Expand Up @@ -81,6 +84,9 @@ def __init__(self,
@t_batch_mode_transform()
def forward(self,
x: Tensor) -> Tensor:
"""
Evaluate the acquisition function on the candidate set x
"""
# x dimensions: b * q * d
x_train = self.model.train_inputs[0][0] # train_inputs is a list of tuples

Expand Down
12 changes: 6 additions & 6 deletions obsidian/campaign/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def plot_interactions(optimizer, cor, clamp=False):
optimizer (BayesianOptimizer): The optimizer object which contains a surrogate that has been fit to data
and can be used to make predictions.
cor (np.ndarray): The correlation matrix representing the parameter interactions.
clamp (bool, optional): Whether to clamp the colorbar range to (0, 1). Defaults to False.
clamp (bool, optional): Whether to clamp the colorbar range to (0, 1). Defaults to ``False``.
Returns:
matplotlib.figure.Figure: The parameter interaction plot
Expand Down Expand Up @@ -112,10 +112,10 @@ def calc_ofat_ranges(optimizer, threshold, X_ref, PI_range=0.7,
threshold (float): The response value threshold (minimum value) which would be considered passing for OFAT variations.
PI_range (float, optional): The prediction interval coverage (fraction of density)
steps (int, optional): The number of steps to use in the search for the OFAT boundaries.
The default value is 100.
The default value is ``100``.
response_id (int, optional): The index of the relevant response within the fitted optimizer object.
The default value is 0.
calc_interacts (bool, optional): Whether or not to return the interaction matrix; default is True.
The default value is ``0``.
calc_interacts (bool, optional): Whether or not to return the interaction matrix; default is ``True``.
Returns:
ofat_ranges (pd.DataFrame): A dataframe describing the min/max OFAT values using each LB, UB, and average prediction.
Expand Down Expand Up @@ -206,9 +206,9 @@ def sensitivity(optimizer,
Args:
optimizer (BayesianOptimizer): The optimizer object which contains a surrogate that has been fit to data
and can be used to make predictions.
dx (float, optional): The perturbation size for calculating the sensitivity. Defaults to 1e-6.
dx (float, optional): The perturbation size for calculating the sensitivity. Defaults to ``1e-6``.
X_ref (pd.DataFrame | None, optional): The reference input values for calculating the sensitivity.
If None, the mean of X_space will be used as the reference. Defaults to None.
If None, the mean of X_space will be used as the reference. Defaults to ``None``.
Returns:
pd.DataFrame: A DataFrame containing the sensitivity values for each parameter in X_space.
Expand Down
11 changes: 10 additions & 1 deletion obsidian/campaign/campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,37 +89,45 @@ def add_data(self, df: pd.DataFrame):
self.data.index.name = 'Observation ID'

def clear_data(self):
"""Clears campaign data"""
self.data = None

@property
def optimizer(self) -> Optimizer:
"""Campaign Optimizer"""
return self._optimizer

def set_optimizer(self, optimizer: Optimizer):
"""Sets the campaign optimizer"""
self._optimizer = optimizer

@property
def designer(self) -> ExpDesigner:
"""Campaign Experimental Designer"""
return self._designer

def set_designer(self, designer: ExpDesigner):
"""Sets the campaign experiment designer"""
self._designer = designer

@property
def objective(self) -> Objective | None:
"""Campaign Objective function"""
return self._objective

def set_objective(self, objective: Objective | None):
"""Sets the campaign objective function"""
self._objective = objective

@property
def target(self):
"""Campaign experimental target(s)"""
return self._target

def set_target(self,
target: Target | list[Target]):
"""
Sets the target for the campaign.
Sets the experimental target context for the campaign.
Args:
target (Target | list[Target] | None): The target or list of targets to set.
Expand Down Expand Up @@ -243,6 +251,7 @@ def load_state(cls,
return new_campaign

def __repr__(self):
"""String representation of object"""
return f"obsidian Campaign for {getattr(self,'y_names', None)}; {getattr(self,'m_exp', 0)} observations"

def initialize(self,
Expand Down
9 changes: 7 additions & 2 deletions obsidian/campaign/explainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ def __repr__(self):

@property
def optimizer(self):
"""Explainer Optimizer object"""
return self._optimizer

def set_optimizer(self, optimizer: Optimizer):
"""Sets the explainer optimizer"""
self._optimizer = optimizer

def shap_explain(self,
Expand Down Expand Up @@ -114,13 +116,15 @@ def f_preds(X):
return

def shap_summary(self) -> Figure:
"""SHAP Summary Plot (Beeswarm)"""
if not self.shap:
raise ValueError('shap explainer is not fit.')

fig = shap.summary_plot(self.shap['values'], self.shap['X_sample'])
return fig

def shap_summary_bar(self) -> Figure:
"""SHAP Summary Plot (Bar Plot / Importance)"""
if not self.shap:
raise ValueError('shap explainer is not fit.')

Expand All @@ -136,7 +140,7 @@ def shap_pdp_ice(self,
ace_opacity: float = 0.5,
ace_linewidth="auto"
) -> Figure:

"""SHAP Partial Dependence Plot with ICE"""
if not self.shap:
raise ValueError('shap explainer is not fit.')

Expand All @@ -156,6 +160,7 @@ def shap_pdp_ice(self,
def shap_single_point(self,
X_new,
X_ref=None):
"""SHAP Pair-wise Marginal Explanations"""
if not self.shap:
raise ValueError('shap explainer is not fit.')

Expand All @@ -181,7 +186,7 @@ def shap_single_point(self,
def cal_sensitivity(self,
dx: float = 1e-6,
X_ref: pd.DataFrame | None = None) -> pd.DataFrame:

"""Local parameter sensitivity analysis"""
df_sens = sensitivity(self.optimizer, dx=dx, X_ref=X_ref)

return df_sens
8 changes: 4 additions & 4 deletions obsidian/constraints/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ def InConstraint_Generic(X_space: ParamSpace,
Args:
X_space (ParamSpace): The parameter space object.
indices (list[float | int], optional): The indices of the parameters to be constrained. Defaults to [0].
coeff (list[float | int | list], optional): The coefficients for the parameters in the constraint equation. Defaults to [0].
rhs (int | float, optional): The right-hand side value of the constraint equation. Defaults to -1.
indices (list[float | int], optional): The indices of the parameters to be constrained. Defaults to ``[0]``.
coeff (list[float | int | list], optional): The coefficients for the parameters in the constraint equation. Defaults to ``[0]``.
rhs (int | float, optional): The right-hand side value of the constraint equation. Defaults to ``-1``.
Returns:
tuple[Tensor, Tensor, Tensor]: A tuple containing the indices, coefficients, and right-hand side value of the constraint.
Expand Down Expand Up @@ -102,7 +102,7 @@ def InConstraint_ConstantDim(X_space: ParamSpace,
Args:
X_space (ParamSpace): The parameter space object.
dim (int): The dimension of the constant parameter.
tol (int | float, optional): The tolerance value. Defaults to 0.01.
tol (int | float, optional): The tolerance value. Defaults to ``0.01``.
Returns:
tuple[callable, bool]: A tuple containing the constraint function and a boolean indicating if it is an inter-point constraint.
Expand Down
5 changes: 3 additions & 2 deletions obsidian/constraints/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

import torch
from torch import Tensor
from typing import Callable

# Negative values imply feasibility!
# Note that these are OUTPUT constraints


def OutConstraint_Blank(target: Target | list[Target]) -> callable:
def OutConstraint_Blank(target: Target | list[Target]) -> Callable:
"""
Dummy constraint function that proposes all samples as feasible.
Expand All @@ -29,7 +30,7 @@ def constraint(samples: Tensor) -> Tensor:


def OutConstraint_L1(target: Target | list[Target],
offset: int | float = 1) -> callable:
offset: int | float = 1) -> Callable:
"""
Calculates the L1 (absolute-value penalized) constraint
Expand Down
7 changes: 4 additions & 3 deletions obsidian/experiment/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(self,
self.seed = seed

def __repr__(self):
"""String representation of object"""
return f"obsidian ExpDesigner(X_space={self.X_space})"

def initialize(self,
Expand All @@ -48,9 +49,9 @@ def initialize(self,
Args:
m_initial (int): The number of experiments to initialize.
method (str, optional): The method to use for initialization. Defaults to 'LHS'.
seed (int | None, optional): The randomization seed. Defaults to None.
sample_custom (Tensor | ArrayLike | None, optional): Custom samples for initialization. Defaults to None.
method (str, optional): The method to use for initialization. Defaults to ``'LHS'``.
seed (int | None, optional): The randomization seed. Defaults to ``None``.
sample_custom (Tensor | ArrayLike | None, optional): Custom samples for initialization. Defaults to ``None``.
Returns:
pd.DataFrame: The initialized experiment design.
Expand Down
6 changes: 4 additions & 2 deletions obsidian/experiment/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ class Simulator:
Attributes:
X_space (ParamSpace): The ParamSpace object representing the allowable space for optimization.
response_function (Callable): The callable function used to convert experiments to responses.
name (str or list[str]): Name of the simulated output(s).
eps (float or list[float]): The simulated error to apply, as the standard deviation of the Standard Normal distribution.
name (str or list[str]): Name of the simulated output(s). Default is ``Response``.
eps (float or list[float]): The simulated error to apply, as the standard deviation of the Standard
Normal distribution. Default is ``0``.
kwargs (dict): Optional hyperparameters for the response function.
Raises:
Expand Down Expand Up @@ -47,6 +48,7 @@ def __init__(self,
self.kwargs = kwargs

def __repr__(self):
"""String representation of object"""
return f" obsidian Simulator(response_function={self.response_function.__name__}, eps={self.eps})"

def simulate(self,
Expand Down
8 changes: 4 additions & 4 deletions obsidian/experiment/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ def factorial_DOE(d: int,
Args:
d (int): Number of dimensions/inputs in the design.
n_CP (int, optional): The number of centerpoints to include in the design, for estimating
uncertainty and curvature. Default is 3.
uncertainty and curvature. Default is ``3``.
shuffle (bool, optional): Whether or not to shuffle the design or leave them in the default run
order. Default is True.
seed (int, optional): Randomization seed. Default is None.
full (bool, optional): Whether or not to run the full DOE. Default is False, which
order. Default is ``True``.
seed (int, optional): Randomization seed. Default is ``None``.
full (bool, optional): Whether or not to run the full DOE. Default is ``False``, which
will lead to an efficient Res4+ design.
Returns:
Expand Down
6 changes: 4 additions & 2 deletions obsidian/objectives/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def __init__(self,

def output_shape(self,
samples: Tensor) -> Tensor:
"""Converts the output to the correct Torch shape based on the multi-output flag"""
return samples.squeeze(-1) if not self._is_mo else samples

@abstractmethod
Expand All @@ -36,16 +37,17 @@ def forward(self,
pass # pragma: no cover

def save_state(self) -> dict:

"""Saves the objective to a state dictionary"""
obj_dict = {'name': self.__class__.__name__,
'state_dict': tensordict_to_dict(self.state_dict())}

return obj_dict

@classmethod
def load_state(cls, obj_dict: dict):
"""Loads the objective from a state dictionary"""
return cls(**obj_dict['state_dict'])

def __repr__(self):
"""String representation of object"""
return f'{self.__class__.__name__} (mo={self._is_mo})'
2 changes: 2 additions & 0 deletions obsidian/objectives/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Method pointers and config for objective functions"""

from .custom import (
Identity_Objective,
Feature_Objective,
Expand Down
Loading

0 comments on commit bd0e7d7

Please sign in to comment.