Source code for queens.distributions.mean_field_normal

#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (c) 2024-2025, QUEENS contributors.
#
# This file is part of QUEENS.
#
# QUEENS is free software: you can redistribute it and/or modify it under the terms of the GNU
# Lesser General Public License as published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version. QUEENS is distributed in the hope that it will
# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You
# should have received a copy of the GNU Lesser General Public License along with QUEENS. If not,
# see <https://www.gnu.org/licenses/>.
#
"""Mean-field normal distribution."""

import numpy as np
import scipy.stats
from scipy.special import erf  # pylint:disable=no-name-in-module

from queens.distributions._distribution import Continuous
from queens.utils.logger_settings import log_init_args


[docs] class MeanFieldNormal(Continuous): """Mean-field normal distribution. Attributes: standard_deviation (np.ndarray): standard deviation vector """ @log_init_args def __init__(self, mean, variance, dimension): """Initialize normal distribution. Args: mean (np.ndarray): mean of the distribution variance (np.ndarray): variance of the distribution dimension (int): dimensionality of the distribution """ mean = np.array(mean) variance = np.array(variance) mean = MeanFieldNormal.get_check_array_dimension_and_reshape(mean, dimension) covariance = MeanFieldNormal.get_check_array_dimension_and_reshape(variance, dimension) self.standard_deviation = np.sqrt(covariance) super().__init__(mean, covariance, dimension)
[docs] def update_variance(self, variance): """Update the variance of the mean field distribution. Args: variance (np.array): New variance vector """ covariance = MeanFieldNormal.get_check_array_dimension_and_reshape(variance, self.dimension) self.covariance = covariance self.standard_deviation = np.sqrt(covariance)
[docs] def update_mean(self, mean): """Update the mean of the mean field distribution. Args: mean (np.array): New mean vector """ mean = MeanFieldNormal.get_check_array_dimension_and_reshape(mean, self.dimension) self.mean = mean
[docs] def cdf(self, x): """Cumulative distribution function. Args: x (np.ndarray): Positions at which the cdf is evaluated Returns: cdf (np.ndarray): cdf at evaluated positions """ z = (x - self.mean) / self.standard_deviation cdf = 0.5 * (1 + erf(z / np.sqrt(2))) cdf = np.prod(cdf, axis=1).reshape(x.shape[0], -1) return cdf
[docs] def draw(self, num_draws=1): """Draw samples. Args: num_draws (int, optional): Number of draws Returns: samples (np.ndarray): Drawn samples from the distribution """ samples = np.random.randn(num_draws, self.dimension) * self.standard_deviation.reshape( 1, -1 ) + self.mean.reshape(1, -1) return samples
[docs] def logpdf(self, x): """Log of the probability density function. Args: x (np.ndarray): Positions at which the log pdf is evaluated Returns: logpdf (np.ndarray): log pdf at evaluated positions """ dist = x - self.mean logpdf = ( -0.5 * self.dimension * np.log(2 * np.pi) - 0.5 * np.sum(np.log(self.covariance)) - 0.5 * np.sum(dist**2 / self.covariance, axis=1) ).flatten() return logpdf
[docs] def grad_logpdf(self, x): """Gradient of the log pdf with respect to x. Args: x (np.ndarray): Positions at which the gradient of log pdf is evaluated Returns: grad_logpdf (np.ndarray): Gradient of the log pdf evaluated at positions """ gradients_batch = -(x - self.mean) / self.covariance gradients_batch = gradients_batch.reshape(x.shape[0], -1) return gradients_batch
[docs] def grad_logpdf_var(self, x): """Gradient of the log pdf with respect to the variance vector. Args: x (np.ndarray): Positions at which the gradient of log pdf is evaluated Returns: grad_logpdf_var (np.ndarray): Gradient of the log pdf w.r.t. the variance at given variance vector and position x """ sample_batch = x.reshape(-1, self.dimension) part_1 = -0.5 * (1 / self.covariance) part_2 = 0.5 * ((sample_batch - self.mean) ** 2 / self.covariance**2) gradient_batch = part_1 + part_2 grad_logpdf_var = gradient_batch.reshape(x.shape[0], -1) return grad_logpdf_var
[docs] def pdf(self, x): """Probability density function. Args: x (np.ndarray): Positions at which the pdf is evaluated Returns: pdf (np.ndarray): pdf at evaluated positions """ pdf = np.exp(self.logpdf(x)) return pdf
[docs] def ppf(self, quantiles): """Percent point function (inverse of cdf — quantiles). Args: quantiles (np.ndarray): Quantiles at which the ppf is evaluated """ self.check_1d() ppf = scipy.stats.norm.ppf( quantiles, loc=self.mean, scale=self.covariance ** (1 / 2) ).reshape(-1) return ppf
[docs] @staticmethod def get_check_array_dimension_and_reshape(input_array, dimension): """Check dimensions and potentially reshape array. Args: input_array (np.ndarray): Input array dimension (int): Dimension of the array Returns: input_array (np.ndarray): Input array with correct dimension """ if not isinstance(input_array, np.ndarray): raise TypeError("Input must be a numpy array.") # allow one dimensional inputs that update the entire array if input_array.size == 1: input_array = np.tile(input_array, dimension) # raise error in case of dimension mismatch if input_array.size != dimension: raise ValueError( "Dimension of input vector and dimension attribute do not match." f"Provided dimension of input vector: {input_array.size}." f"Provided dimension was: {dimension}." ) return input_array