from __future__ import division
import numpy as np
from .domain import TOL
from .box import BoxDomain
from .random import RandomDomain
from .normal import NormalDomain
# TODO: Ensure sampling is still correct (IMPORTANT FOR DUU Solution)
[docs]class LogNormalDomain(BoxDomain, RandomDomain):
r"""A one-dimensional domain described by a log-normal distribution.
Given a normal distribution :math:`\mathcal{N}(\boldsymbol{\mu}, \boldsymbol{\Gamma})`,
the log normal is described by
.. math::
x = \alpha + \beta e^y, \quad y \sim \mathcal{N}(\boldsymbol{\mu}, \boldsymbol{\Gamma})
where :math:`\alpha` is an offset and :math:`\beta` is a scaling coefficient.
Parameters
----------
mean: float
mean of normal distribution feeding the log-normal distribution
cov: float, optional
covariance of normal distribution feeding the log-normal distribution
offset: float, optional
Shift the distribution
scaling: float or np.ndarray
Scale the output of the log-normal distribution
truncate: float [0,1)
Truncate the tails of the distribution
"""
def __init__(self, mean, cov = 1., offset = 0., scaling = 1., truncate = None, names = None):
self.tol = 1e-6
self.normal_domain = NormalDomain(mean, cov, truncate = truncate)
assert len(self.normal_domain) == 1, "Only defined for one-dimensional distributions"
self.mean = float(self.normal_domain.mean)
self.cov = float(self.normal_domain.cov)
self.scaling = float(scaling)
self.offset = float(offset)
self.truncate = truncate
# Determine bounds
# Because this doesn't have a convex relationship to the multivariate normal
# truncated domains, we manually specify these here as they cannot be inferred
# from the (non-existant) quadratic constraints as in the NormalDomain case.
if self.truncate is not None:
self._lb = self.offset + self.scaling*np.exp(self.normal_domain.norm_lb)
self._ub = self.offset + self.scaling*np.exp(self.normal_domain.norm_ub)
else:
self._lb = 0.*np.ones(1)
self._ub = np.inf*np.ones(1)
self._init_names(names)
def __len__(self):
return len(self.normal_domain)
def _sample(self, draw = 1):
X = self.normal_domain.sample(draw)
return np.array(self.offset).reshape(-1,1) + self.scaling*np.exp(X)
def _normalized_domain(self, **kwargs):
names_norm = [name + ' (normalized)' for name in self.names]
if self.truncate is not None:
c = self._center()
D = float(self._normalize_der())
return LogNormalDomain(self.normal_domain.mean, self.normal_domain.cov,
offset = D*(self.offset - c) , scaling = D*self.scaling, truncate = self.truncate, names = names_norm)
else:
return self
def _pdf(self, X):
X_norm = (X - self.offset)/self.scaling
p = np.exp(-(np.log(X_norm) - self.mean)**2/(2*self.cov))/(X_norm*self.cov*np.sqrt(2*np.pi))
return p