Ejemplo n.º 1
0
    def random_state(self, seed: RandomStateArgType):
        """Get or set the RandomState object of the random variable.

        This can be either None or an existing RandomState object. If
        None (or np.random), use the RandomState singleton used by
        np.random. If already a RandomState instance, use it. If an int,
        use a new RandomState instance seeded with seed.
        """
        self._random_state = _utils.as_random_state(seed)
Ejemplo n.º 2
0
    def __init__(
        self,
        shape: ShapeArgType,
        dtype: DTypeArgType,
        random_state: RandomStateArgType = None,
        parameters: Optional[Dict[str, Any]] = None,
        sample: Optional[Callable[[ShapeType], _ValueType]] = None,
        in_support: Optional[Callable[[_ValueType], bool]] = None,
        cdf: Optional[Callable[[_ValueType], np.float_]] = None,
        logcdf: Optional[Callable[[_ValueType], np.float_]] = None,
        quantile: Optional[Callable[[FloatArgType], _ValueType]] = None,
        mode: Optional[Callable[[], _ValueType]] = None,
        median: Optional[Callable[[], _ValueType]] = None,
        mean: Optional[Callable[[], _ValueType]] = None,
        cov: Optional[Callable[[], _ValueType]] = None,
        var: Optional[Callable[[], _ValueType]] = None,
        std: Optional[Callable[[], _ValueType]] = None,
        entropy: Optional[Callable[[], np.float_]] = None,
        as_value_type: Optional[Callable[[Any], _ValueType]] = None,
    ):
        # pylint: disable=too-many-arguments,too-many-locals
        """Create a new random variable."""
        self.__shape = _utils.as_shape(shape)

        # Data Types
        self.__dtype = np.dtype(dtype)
        self.__median_dtype = RandomVariable.infer_median_dtype(self.__dtype)
        self.__moment_dtype = RandomVariable.infer_moment_dtype(self.__dtype)

        self._random_state = _utils.as_random_state(random_state)

        # Probability distribution of the random variable
        self.__parameters = parameters.copy() if parameters is not None else {}

        self.__sample = sample

        self.__in_support = in_support
        self.__cdf = cdf
        self.__logcdf = logcdf
        self.__quantile = quantile

        # Properties of the random variable
        self.__mode = mode
        self.__median = median
        self.__mean = mean
        self.__cov = cov
        self.__var = var
        self.__std = std
        self.__entropy = entropy

        # Utilities
        self.__as_value_type = as_value_type
Ejemplo n.º 3
0
def random_spd_matrix(
    dim: IntArgType,
    spectrum: Sequence = None,
    random_state: Optional[RandomStateArgType] = None,
) -> np.ndarray:
    """Random symmetric positive definite matrix.

    Constructs a random symmetric positive definite matrix from a given spectrum. An
    orthogonal matrix :math:`Q` with :math:`\\operatorname{det}(Q)` (a rotation) is
    sampled with respect to the Haar measure and the diagonal matrix
    containing the eigenvalues is rotated accordingly resulting in :math:`A=Q
    \\operatorname{diag}(\\lambda_1, \\dots, \\lambda_n)Q^\\top`. If no spectrum is
    provided, one is randomly drawn from a Gamma distribution.

    Parameters
    ----------
    dim
        Matrix dimension.
    spectrum
        Eigenvalues of the matrix.
    random_state
        Random state of the random variable. If None (or np.random), the global
        :mod:`numpy.random` state is used. If integer, it is used to seed the local
        :class:`~numpy.random.RandomState` instance.

    See Also
    --------
    random_sparse_spd_matrix : Generate a random sparse symmetric positive definite matrix.

    Examples
    --------
    >>> from probnum.problems.zoo.linalg import random_spd_matrix
    >>> mat = random_spd_matrix(dim=5, random_state=0)
    >>> mat
    array([[10.49868572, -0.80840778,  0.79781892,  1.9229059 ,  0.73413367],
           [-0.80840778, 15.79117417,  0.52641887, -1.8727916 , -0.9309482 ],
           [ 0.79781892,  0.52641887, 15.56457452,  1.26004438, -1.44969733],
           [ 1.9229059 , -1.8727916 ,  1.26004438,  8.59057287, -0.44955394],
           [ 0.73413367, -0.9309482 , -1.44969733, -0.44955394,  9.77198568]])

    Check for symmetry and positive definiteness.

    >>> np.all(mat == mat.T)
    True
    >>> np.linalg.eigvals(mat)
    array([ 6.93542496, 10.96494454,  9.34928449, 16.25401501, 16.71332395])
    """
    # Initialization
    random_state = _utils.as_random_state(random_state)

    if spectrum is None:
        # Create a custom ordered spectrum if none is given.
        spectrum_shape: float = 10.0
        spectrum_scale: float = 1.0
        spectrum_offset: float = 0.0

        spectrum = scipy.stats.gamma.rvs(
            spectrum_shape,
            loc=spectrum_offset,
            scale=spectrum_scale,
            size=dim,
            random_state=random_state,
        )
        spectrum = np.sort(spectrum)[::-1]

    else:
        spectrum = np.asarray(spectrum)
        if not np.all(spectrum > 0):
            raise ValueError(
                f"Eigenvalues must be positive, but are {spectrum}.")

    # Early exit for d=1 -- special_ortho_group does not like this case.
    if dim == 1:
        return spectrum.reshape((1, 1))

    # Draw orthogonal matrix with respect to the Haar measure
    orth_mat = scipy.stats.special_ortho_group.rvs(dim,
                                                   random_state=random_state)
    spd_mat = orth_mat @ np.diag(spectrum) @ orth_mat.T

    # Symmetrize to avoid numerically not symmetric matrix
    # Since A commutes with itself (AA' = A'A = AA) the eigenvalues do not change.
    return 0.5 * (spd_mat + spd_mat.T)
Ejemplo n.º 4
0
def random_sparse_spd_matrix(
    dim: IntArgType,
    density: float,
    chol_entry_min: float = 0.1,
    chol_entry_max: float = 1.0,
    random_state: Optional[RandomStateArgType] = None,
) -> np.ndarray:
    """Random sparse symmetric positive definite matrix.

    Constructs a random sparse symmetric positive definite matrix for a given degree
    of sparsity. The matrix is constructed from its Cholesky factor :math:`L`. Its
    diagonal is set to one and all other entries of the lower triangle are sampled
    from a uniform distribution with bounds :code:`[chol_entry_min, chol_entry_max]`.
    The resulting sparse matrix is then given by :math:`A=LL^\\top`.

    Parameters
    ----------
    dim
        Matrix dimension.
    density
        Degree of sparsity of the off-diagonal entries of the Cholesky factor.
        Between 0 and 1 where 1 represents a dense matrix.
    chol_entry_min
        Lower bound on the entries of the Cholesky factor.
    chol_entry_max
        Upper bound on the entries of the Cholesky factor.
    random_state
        Random state of the random variable. If None (or np.random), the global
        :mod:`numpy.random` state is used. If integer, it is used to seed the local
        :class:`~numpy.random.RandomState` instance.

    See Also
    --------
    random_spd_matrix : Generate a random symmetric positive definite matrix.

    Examples
    --------
    >>> from probnum.problems.zoo.linalg import random_sparse_spd_matrix
    >>> sparsemat = random_sparse_spd_matrix(dim=5, density=0.1, random_state=42)
    >>> sparsemat
    array([[1.        , 0.        , 0.        , 0.        , 0.        ],
           [0.        , 1.        , 0.        , 0.        , 0.        ],
           [0.        , 0.        , 1.        , 0.        , 0.24039507],
           [0.        , 0.        , 0.        , 1.        , 0.        ],
           [0.        , 0.        , 0.24039507, 0.        , 1.05778979]])
    """

    # Initialization
    random_state = _utils.as_random_state(random_state)
    if not 0 <= density <= 1:
        raise ValueError(f"Density must be between 0 and 1, but is {density}.")
    chol = np.eye(dim)
    num_off_diag_cholesky = int(0.5 * dim * (dim - 1))
    num_nonzero_entries = int(num_off_diag_cholesky * density)

    if num_nonzero_entries > 0:
        # Draw entries of lower triangle (below diagonal) according to sparsity level
        entry_ids = np.mask_indices(n=dim, mask_func=np.tril, k=-1)
        idx_samples = random_state.choice(a=num_off_diag_cholesky,
                                          size=num_nonzero_entries,
                                          replace=False)
        nonzero_entry_ids = (entry_ids[0][idx_samples],
                             entry_ids[1][idx_samples])

        # Fill Cholesky factor
        chol[nonzero_entry_ids] = random_state.uniform(
            low=chol_entry_min, high=chol_entry_max, size=num_nonzero_entries)

    return chol @ chol.T
Ejemplo n.º 5
0
def probsolve_qp(
    fun: Callable[[FloatArgType], FloatArgType],
    fun_params0: Optional[Union[np.ndarray, randvars.RandomVariable]] = None,
    assume_fun: Optional[str] = None,
    tol: FloatArgType = 10**-5,
    maxiter: IntArgType = 10**4,
    noise_cov: Optional[Union[np.ndarray, linops.LinearOperator]] = None,
    callback: Optional[Callable[
        [FloatArgType, FloatArgType, randvars.RandomVariable], None]] = None,
    random_state: RandomStateArgType = None,
) -> Tuple[float, randvars.RandomVariable, randvars.RandomVariable, Dict]:
    """Probabilistic 1D Quadratic Optimization.

    PN method solving unconstrained one-dimensional (noisy) quadratic
    optimization problems only needing access to function evaluations.

    Parameters
    ----------
    fun :
        Quadratic objective function to optimize.
    fun_params0 :
        *(shape=(3, ) or (3, 1))* -- Prior on the parameters of the
        objective function or initial guess for the parameters.
    assume_fun :
        Type of probabilistic numerical method to use. The available
        options are

        =====================  =============
         automatic selection   ``None``
         exact observations    ``"exact"``
         noisy observations    ``"noise"``
        =====================  =============

        If ``None`` the type of method is inferred from the problem
        ``fun`` and prior ``fun_params0``.
    tol :
        Convergence tolerance.
    maxiter :
        Maximum number of iterations.
    noise_cov :
        *(shape=(3, 3))* -- Covariance of the additive noise on the parameters
        of the noisy objective function.
    callback :
        Callback function returning intermediate quantities of the
        optimization loop. Note that depending on the function
        supplied, this can slow down the solver considerably.
    random_state :
        Random state of the solver. If None (or ``np.random``), the global
        ``np.random`` state is used. If integer, it is used to seed the local
        :class:`~numpy.random.RandomState` instance.

    Returns
    -------
    x_opt :
        Estimated minimum of the objective function.
    fun_opt :
        Belief over the optimal value of the objective function.
    fun_params :
        Belief over the parameters of the objective function.
    info :
        Additional information about the optimization, e.g. convergence.

    Examples
    --------
    >>> f = lambda x: 2.0 * x ** 2 - 0.75 * x + 0.2
    >>> x_opt, fun_opt, fun_params_opt, info = probsolve_qp(f)
    >>> print(info["iter"])
    3
    """

    # Choose a variant of the PN method
    if assume_fun is None:
        # Infer PN variant to use based on the problem
        if noise_cov is not None or fun(1.0) != fun(1.0):
            assume_fun = "noise"
        else:
            assume_fun = "exact"

    # Select appropriate prior based on the problem
    fun_params0 = _choose_prior(fun_params0=fun_params0)

    # Create a local instance of the random number generator if none is provided
    random_state = _utils.as_random_state(random_state)

    if assume_fun == "exact":
        # Exact 1D quadratic optimization
        probquadopt = ProbabilisticQuadraticOptimizer(
            fun_params_prior=fun_params0,
            policy=partial(stochastic_policy, random_state=random_state),
            observation_operator=function_evaluation,
            belief_update=partial(gaussian_belief_update,
                                  noise_cov=np.zeros(3)),
            stopping_criteria=[
                partial(parameter_uncertainty, abstol=tol, reltol=tol),
                partial(maximum_iterations, maxiter=maxiter),
            ],
        )
    elif assume_fun == "noise":
        # Noisy 1D quadratic optimization
        probquadopt = ProbabilisticQuadraticOptimizer(
            fun_params_prior=fun_params0,
            policy=partial(explore_exploit_policy, random_state=random_state),
            observation_operator=function_evaluation,
            belief_update=partial(gaussian_belief_update, noise_cov=noise_cov),
            stopping_criteria=[
                partial(parameter_uncertainty, abstol=tol, reltol=tol),
                partial(maximum_iterations, maxiter=maxiter),
            ],
        )
    else:
        raise ValueError(
            f'Unknown assumption on function evaluations: "{assume_fun}".')

    # Run optimization iteration
    x_opt0, fun_opt0, fun_params0, info = probquadopt.optimize(
        fun=fun, callback=callback)

    # Return output with information (e.g. on convergence)
    info["assume_fun"] = assume_fun
    return x_opt0, fun_opt0, fun_params0, info