import numpy as np
from typing import Union
from socialchoicekit.profile_utils import Profile, StrictProfile, ValuationProfile
[docs]class BaseValuationProfileGenerator:
"""
The abstract cardinal utility generator. This class should not be instantiated directly.
The data generator assumes that the utilities are generated for the normalized social choice setting.
Parameters
----------
seed: Union[int, None]
The seed for the random number generator. If None, the random number generator will not be seeded.
"""
def __init__(
self,
seed: Union[int, None] = None,
):
self.seed = seed
[docs] def generate(
self,
profile: Profile,
) -> ValuationProfile:
"""
Generates a cardinal profile based on the inputted ordinal profile.
Parameters
----------
profile: StrictProfile
A (N, M) array, where N is the number of agents and M is the number of alternatives. The element at (i, j) indicates the agent's ordinal utility for alternative j, where 1 is the most preferred alternative and M is the least preferred alternative. If the agent finds an item or alternative unacceptable, the element would be np.nan.
Returns
-------
ValuationProfile
A (N, M) array, where N is the number of agents and M is the number of alternatives. The element at (i, j) indicates the agent's cardinal utility for alternative j. If the agent finds an item or alternative unacceptable, the element would be np.nan.
"""
raise NotImplementedError
[docs]class NormalValuationProfileGenerator(BaseValuationProfileGenerator):
"""
A simple data generator that first generates a valuation profile (cardinal utilities) based on a normal probability distribution, and assigns the normalized generated utilities to alternatives based on the inputted profile (ordinal utilities).
Parameters
----------
mean: float
The mean of the normal distribution. 0.5 by default.
varaince: float
The variance of the normal distribution. 0.2 by default. Any generated values below 0 will be clipped to 0.
seed: Union[int, None]
The seed for the random number generator. If None, the random number generator will not be seeded.
"""
def __init__(
self,
mean: float,
variance: float,
seed: Union[int, None] = None,
):
self.mean = mean
self.variance = variance
super().__init__(seed=seed)
[docs] def generate(
self,
profile: StrictProfile,
) -> ValuationProfile:
"""
Generates a cardinal profile based on the inputted ordinal profile.
Parameters
----------
profile: StrictProfile
A (N, M) array, where N is the number of agents and M is the number of alternatives. The element at (i, j) indicates the agent's ordinal utility for alternative j, where 1 is the most preferred alternative and M is the least preferred alternative. If the agent finds an item or alternative unacceptable, the element would be np.nan.
Returns
-------
ValuationProfile
A (N, M) array, where N is the number of agents and M is the number of alternatives. The element at (i, j) indicates the agent's cardinal utility for alternative j. If the agent finds an item or alternative unacceptable, the element would be np.nan.
"""
if self.seed is not None:
np.random.seed(self.seed)
n = profile.shape[0]
m = profile.shape[1]
ranked_profile = np.argsort(profile, axis=1).view(np.ndarray)
# Preserve np.nan
ans = profile.view(np.ndarray) * 0.0
for agent in range(n):
num_not_nan = np.count_nonzero(~np.isnan(profile[agent]))
utilities = np.random.normal(size=num_not_nan, loc=self.mean, scale=np.sqrt(self.variance))
# Clip negative values to 0
utilities = np.where(utilities < 0, 0, utilities)
# Descending sort and normalize
utilities = np.sort(utilities)[::-1] / np.sum(utilities)
for item_rank in range(m):
item = ranked_profile[agent, item_rank]
if np.isnan(profile[agent, item]):
break
ans[agent, item] = utilities[item_rank]
return ValuationProfile.of(ans)