Source code for popgames.revision_protocol

from __future__ import annotations

from abc import ABC, abstractmethod
import numpy as np

from popgames.utilities.input_validators import check_scalar_value_bounds

__all__ = [
    'RevisionProtocolABC',
    'Softmax',
    'Smith',
    'BNN'
]

[docs] class RevisionProtocolABC(ABC): """ Abstract base class for revision protocols. """
[docs] @abstractmethod def __call__( self, p : np.ndarray, x : np.ndarray ) -> np.ndarray: """ Subclasses must implement this method to enable the revision protocol to be called as a function. Args: p (np.ndarray): The payoff vector with shape (n, 1). x (np.ndarray): The population state vector with shape (n, 1). Returns: np.ndarray: The switching probabilities as a matrix with shape (n, n). """
[docs] class Softmax(RevisionProtocolABC): """ Softmax revision protocol. Also known as Logit-Choice revision protocol. """ def __init__( self, eta : float ) -> None: """ Initialize the Softmax revision protocol object. Args: eta (float): The `temperature` or `noise` parameter. """ check_scalar_value_bounds( arg=eta, arg_name='eta', strictly_positive=True ) self.eta = eta
[docs] def __call__( self, p : np.ndarray, x : np.ndarray ) -> np.ndarray: """ Evaluate the Softmax revision protocol. Args: p (np.ndarray): The payoff vector with shape (n, 1). x (np.ndarray): The population state vector with shape (n, 1). Returns: np.ndarray: The switching probabilities as a matrix with shape (n, n). Examples: >>> import numpy as np >>> from popgames.revision_protocol import Softmax >>> softmax = Softmax(eta=1) >>> p = np.array([1, -1, 2]).reshape(3, 1) >>> x = np.array([0.1, 0.7, 0.2]).reshape(3, 1) >>> softmax(p, x) array([[0.25949646, 0.25949646, 0.25949646], [0.03511903, 0.03511903, 0.03511903], [0.70538451, 0.70538451, 0.70538451]]) """ logits = np.exp((p/self.eta) - np.max(p/self.eta)) probabilities = logits/(logits.sum()) return np.dot(probabilities, np.ones_like(probabilities).T)
[docs] class Smith(RevisionProtocolABC): """ Smith revision protocol. """ def __init__( self, scale : float ) -> None: """ Initialize the Smith revision protocol object. Args: scale (float): The scale parameter to ensure well-posed probabilities. """ check_scalar_value_bounds( arg=scale, arg_name='scale', strictly_positive=True ) self.scale = scale
[docs] def __call__( self, p : np.ndarray, x : np.ndarray ) -> np.ndarray: """ Evaluate the Smith revision protocol. Args: p (np.ndarray): The payoff vector with shape (n, 1). x (np.ndarray): The population state vector with shape (n, 1). Returns: np.ndarray: The switching probabilities as a matrix with shape (n, n). Examples: >>> import numpy as np >>> from popgames.revision_protocol import Smith >>> smith = Smith(scale=0.1) >>> p = np.array([1, -1, 2]).reshape(3, 1) >>> x = np.array([0.1, 0.7, 0.2]).reshape(3, 1) >>> smith(p, x) array([[0. , 0.2, 0. ], [0. , 0. , 0. ], [0.1, 0.3, 0. ]]) """ return np.maximum(p - p.T, 0)*self.scale
[docs] class BNN(RevisionProtocolABC): """ Brown-von Neumann-Nash (BNN) revision protocol. """ def __init__( self, scale : float ) -> None: """ Initialize the BNN revision protocol object. Args: scale (float): The scale parameter to ensure well-posed probabilities. """ check_scalar_value_bounds( arg=scale, arg_name='scale', strictly_positive=True ) self.scale = scale
[docs] def __call__( self, p : np.ndarray, x : np.ndarray ) -> np.ndarray: """ Evaluate the BNN revision protocol. Args: p (np.ndarray): The payoff vector with shape (n, 1). x (np.ndarray): The population state vector with shape (n, 1). Returns: np.ndarray: The switching probabilities as a matrix with shape (n, n). Examples: >>> import numpy as np >>> from popgames.revision_protocol import BNN >>> bnn = BNN(scale=0.1) >>> p = np.array([1, -1, 2]).reshape(3, 1) >>> x = np.array([0.1, 0.7, 0.2]).reshape(3, 1) >>> bnn(p, x) array([[0.12, 0.12, 0.12], [0. , 0. , 0. ], [0.22, 0.22, 0.22]]) """ p_hat = np.dot(x.T, p)/(x.sum()) delta_p = p - p_hat[0] return np.maximum(np.dot(delta_p, np.ones_like(delta_p).T), 0)*self.scale