Module src.TokenLab.simulationcomponents.usergrowthclasses
Created on Fri Nov 18 12:18:42 2022
@author: stylianoskampakis
Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Nov 18 12:18:42 2022
@author: stylianoskampakis
"""
from typing import List, Dict,Union
from baseclasses import *
from utils.helpers import log_saturated_space
import copy
import scipy
import warnings
import time
class UserGrowth(Controller):
"""
Base class that models user growth. Dependencies include AgentPool and TokenEconomy.
"""
def __init__(self):
super(UserGrowth,self).__init__()
self.num_users=0
#conditional dependence, it is used only in conditions
self.dependencies={AgentPool:None,TokenEconomy:None}
return None
def execute(self)->int:
self.iteration = self.iteration + 1
if hasattr(self,'_num_users_store'):
#this is only for precomputed user populations, like UserGrowth_Spaced
self.num_users=self._num_users_store[self.iteration-1]
return self.num_users
def get_num_users(self)->int:
return self.num_users
def reset(self)->None:
"""
Resets the iteration counter to 0.
"""
self.iteration=0
class UserGrowth_Spaced(UserGrowth):
"""
Creates the number of users (for a predetermined number of iterations)
using a function that is spacing out users between min and max. Options include:
linearspace, logspace, geomspace
The class pre-calculates all users internally and stores them in _num_users_store.
When executed it simply returns the users at the current iteration of its lifecycle.
"""
def __init__(self,initial_users:int,max_users:int,num_steps:int,
space_function:Union[np.linspace,np.logspace,np.geomspace,log_saturated_space],name:str=None,
noise_addon:AddOn=None,use_difference:bool=False)->None:
"""
use_difference: If True, it will calculate and return the difference between time steps. Essentially, the user
base will grow according to the difference between the steps in the sequence.
Otherwise, it returns the actual value.
For example:
initial_users=100
growth=[100,120,150,200]
final=[100,120,150,200]
with difference:
final=[100,0,20,30,50]
"""
super(UserGrowth_Spaced,self).__init__()
self._initial_users=initial_users
self.max_users=max_users
self.num_steps=num_steps
self.space_function=space_function
self._noise_component=noise_addon
self.name=name
self._num_users_store_original=np.round(self.space_function(start=self._initial_users,
stop=self.max_users,num=num_steps)).astype(int)
#applies the noise addon. If a value is below 0, then it is kept at 0.
if self._noise_component!=None:
dummy=[]
for i in range(len(self._num_users_store)):
temporary= self._num_users_store_original[i]+ self._noise_component.apply(**{'value':self._num_users_store_original[i]})
if temporary>=0:
dummy.append(temporary)
else:
dummy.append(self._num_users_store_original[i])
self._num_users_store=dummy
#self.num_users=self._initial_users
if use_difference:
self._num_users_store_original=[self._initial_users]+np.diff(self._num_users_store_original).tolist()
self._num_users_store=copy.deepcopy(self._num_users_store_original)
self.max_iterations=num_steps
def execute(self)->int:
users=super(UserGrowth_Spaced,self).execute()
return users
def reset(self)->None:
"""
recalculates the noise component and sets the user counter back to 0
"""
self._num_users_store=copy.deepcopy(self._num_users_store_original)
#applies the noise addon. If a value is below 0, then it is kept at 0.
if self._noise_component!=None:
dummy=[]
for i in range(len(self._num_users_store)):
temporary= self._num_users_store_original[i]+ self._noise_component.apply(**{'value':self._num_users_store_original[i]})
if temporary>=0:
dummy.append(temporary)
else:
dummy.append(self._num_users_store_original[i])
self._num_users_store=dummy
self.iteration=0
self.num_users=self._initial_users
def get_users_store(self)->List:
"""
Returns the list that contains the pre-calculated number of users. This is a
private variable.
"""
return self._num_users_store
class UserGrowth_Constant(UserGrowth):
"""
Simplest type of usergrowth class. The user growth simply stays constant.
"""
def __init__(self,constant:int):
"""
Parameters
----------
constant : int
The total number of users.
"""
super(UserGrowth_Constant,self).__init__()
self.num_users=constant
class UserGrowth_Stochastic(UserGrowth):
"""
UserGrowth that simply samples a random value during each iteration.
"""
def __init__(self,user_growth_distribution:scipy.stats=scipy.stats.poisson,
user_growth_dist_parameters:Union[List,Dict]=[{'mu':1000}],add_to_userbase:bool=False,
num_initial_users:int=0):
"""
Parameters
----------
user_growth_distribution : scipy.stats, optional
The distribution to sample values from. The default is scipy.stats.poisson.
user_growth_dist_parameters : Union[List,Dict], optional
Either a list of dictionaries, or a dictionary. If a list is provided
then the parameters change at every iteration. The default is [{'mu':1000}].
add_to_user_base : bool, optional
If False, then the actual number of users is sampled from the distribution. If True,
then this number of users is added to the total number of users. Default is false.
num_initial_users : int, optional
The number of initial users. Default is 0
"""
super(UserGrowth_Stochastic,self).__init__()
self.user_growth_distribution=user_growth_distribution
self.user_growth_dist_parameters=user_growth_dist_parameters
self.num_users=num_initial_users
self.add_to_userbase=add_to_userbase
return None
def execute(self):
seed=int(int(time.time())*np.random.rand())
if type(self.user_growth_dist_parameters)==list:
try:
params=self.user_growth_dist_parameters[self.iteration]
except:
warnings.warn("Iterations for UserGrowth stochastic exhausted. Simply using the last item on the list of parameters.")
params=self.user_growth_dist_parameters[-1]
else:
params=self.user_growth_dist_parameters
self._active_params=params
value=self.user_growth_distribution.rvs(size=1,random_state=seed,**params)[0]
if self.add_to_userbase:
self.num_users+=value
else:
self.num_users=value
self.iteration+=1
return self.num_users
Classes
class UserGrowth
-
Base class that models user growth. Dependencies include AgentPool and TokenEconomy.
Expand source code
class UserGrowth(Controller): """ Base class that models user growth. Dependencies include AgentPool and TokenEconomy. """ def __init__(self): super(UserGrowth,self).__init__() self.num_users=0 #conditional dependence, it is used only in conditions self.dependencies={AgentPool:None,TokenEconomy:None} return None def execute(self)->int: self.iteration = self.iteration + 1 if hasattr(self,'_num_users_store'): #this is only for precomputed user populations, like UserGrowth_Spaced self.num_users=self._num_users_store[self.iteration-1] return self.num_users def get_num_users(self)->int: return self.num_users def reset(self)->None: """ Resets the iteration counter to 0. """ self.iteration=0
Ancestors
- baseclasses.Controller
Subclasses
Methods
def execute(self) ‑> int
-
Expand source code
def execute(self)->int: self.iteration = self.iteration + 1 if hasattr(self,'_num_users_store'): #this is only for precomputed user populations, like UserGrowth_Spaced self.num_users=self._num_users_store[self.iteration-1] return self.num_users
def get_num_users(self) ‑> int
-
Expand source code
def get_num_users(self)->int: return self.num_users
def reset(self) ‑> None
-
Resets the iteration counter to 0.
Expand source code
def reset(self)->None: """ Resets the iteration counter to 0. """ self.iteration=0
class UserGrowth_Constant (constant: int)
-
Simplest type of usergrowth class. The user growth simply stays constant.
Parameters
constant
:int
- The total number of users.
Expand source code
class UserGrowth_Constant(UserGrowth): """ Simplest type of usergrowth class. The user growth simply stays constant. """ def __init__(self,constant:int): """ Parameters ---------- constant : int The total number of users. """ super(UserGrowth_Constant,self).__init__() self.num_users=constant
Ancestors
- UserGrowth
- baseclasses.Controller
Inherited members
class UserGrowth_Spaced (initial_users: int, max_users: int, num_steps: int, space_function: Union[linspace, logspace, geomspace, log_saturated_space], name: str = None, noise_addon: baseclasses.AddOn = None, use_difference: bool = False)
-
Creates the number of users (for a predetermined number of iterations) using a function that is spacing out users between min and max. Options include:
linearspace, logspace, geomspace
The class pre-calculates all users internally and stores them in _num_users_store.
When executed it simply returns the users at the current iteration of its lifecycle.
use_difference: If True, it will calculate and return the difference between time steps. Essentially, the user base will grow according to the difference between the steps in the sequence.
Otherwise, it returns the actual value.
For example: initial_users=100 growth=[100,120,150,200] final=[100,120,150,200]
with difference: final=[100,0,20,30,50]
Expand source code
class UserGrowth_Spaced(UserGrowth): """ Creates the number of users (for a predetermined number of iterations) using a function that is spacing out users between min and max. Options include: linearspace, logspace, geomspace The class pre-calculates all users internally and stores them in _num_users_store. When executed it simply returns the users at the current iteration of its lifecycle. """ def __init__(self,initial_users:int,max_users:int,num_steps:int, space_function:Union[np.linspace,np.logspace,np.geomspace,log_saturated_space],name:str=None, noise_addon:AddOn=None,use_difference:bool=False)->None: """ use_difference: If True, it will calculate and return the difference between time steps. Essentially, the user base will grow according to the difference between the steps in the sequence. Otherwise, it returns the actual value. For example: initial_users=100 growth=[100,120,150,200] final=[100,120,150,200] with difference: final=[100,0,20,30,50] """ super(UserGrowth_Spaced,self).__init__() self._initial_users=initial_users self.max_users=max_users self.num_steps=num_steps self.space_function=space_function self._noise_component=noise_addon self.name=name self._num_users_store_original=np.round(self.space_function(start=self._initial_users, stop=self.max_users,num=num_steps)).astype(int) #applies the noise addon. If a value is below 0, then it is kept at 0. if self._noise_component!=None: dummy=[] for i in range(len(self._num_users_store)): temporary= self._num_users_store_original[i]+ self._noise_component.apply(**{'value':self._num_users_store_original[i]}) if temporary>=0: dummy.append(temporary) else: dummy.append(self._num_users_store_original[i]) self._num_users_store=dummy #self.num_users=self._initial_users if use_difference: self._num_users_store_original=[self._initial_users]+np.diff(self._num_users_store_original).tolist() self._num_users_store=copy.deepcopy(self._num_users_store_original) self.max_iterations=num_steps def execute(self)->int: users=super(UserGrowth_Spaced,self).execute() return users def reset(self)->None: """ recalculates the noise component and sets the user counter back to 0 """ self._num_users_store=copy.deepcopy(self._num_users_store_original) #applies the noise addon. If a value is below 0, then it is kept at 0. if self._noise_component!=None: dummy=[] for i in range(len(self._num_users_store)): temporary= self._num_users_store_original[i]+ self._noise_component.apply(**{'value':self._num_users_store_original[i]}) if temporary>=0: dummy.append(temporary) else: dummy.append(self._num_users_store_original[i]) self._num_users_store=dummy self.iteration=0 self.num_users=self._initial_users def get_users_store(self)->List: """ Returns the list that contains the pre-calculated number of users. This is a private variable. """ return self._num_users_store
Ancestors
- UserGrowth
- baseclasses.Controller
Methods
def execute(self) ‑> int
-
Expand source code
def execute(self)->int: users=super(UserGrowth_Spaced,self).execute() return users
def get_users_store(self) ‑> List
-
Returns the list that contains the pre-calculated number of users. This is a private variable.
Expand source code
def get_users_store(self)->List: """ Returns the list that contains the pre-calculated number of users. This is a private variable. """ return self._num_users_store
def reset(self) ‑> None
-
recalculates the noise component and sets the user counter back to 0
Expand source code
def reset(self)->None: """ recalculates the noise component and sets the user counter back to 0 """ self._num_users_store=copy.deepcopy(self._num_users_store_original) #applies the noise addon. If a value is below 0, then it is kept at 0. if self._noise_component!=None: dummy=[] for i in range(len(self._num_users_store)): temporary= self._num_users_store_original[i]+ self._noise_component.apply(**{'value':self._num_users_store_original[i]}) if temporary>=0: dummy.append(temporary) else: dummy.append(self._num_users_store_original[i]) self._num_users_store=dummy self.iteration=0 self.num_users=self._initial_users
class UserGrowth_Stochastic (user_growth_distribution:
= <scipy.stats._discrete_distns.poisson_gen object>, user_growth_dist_parameters: Union[List, Dict] = [{'mu': 1000}], add_to_userbase: bool = False, num_initial_users: int = 0) -
UserGrowth that simply samples a random value during each iteration.
Parameters
user_growth_distribution
:scipy.stats
, optional- The distribution to sample values from. The default is scipy.stats.poisson.
user_growth_dist_parameters
:Union[List,Dict]
, optional- Either a list of dictionaries, or a dictionary. If a list is provided then the parameters change at every iteration. The default is [{'mu':1000}].
add_to_user_base
:bool
, optional
If False, then the actual number of users is sampled from the distribution. If True, then this number of users is added to the total number of users. Default is false.
num_initial_users
:int
, optional
The number of initial users. Default is 0
Expand source code
class UserGrowth_Stochastic(UserGrowth): """ UserGrowth that simply samples a random value during each iteration. """ def __init__(self,user_growth_distribution:scipy.stats=scipy.stats.poisson, user_growth_dist_parameters:Union[List,Dict]=[{'mu':1000}],add_to_userbase:bool=False, num_initial_users:int=0): """ Parameters ---------- user_growth_distribution : scipy.stats, optional The distribution to sample values from. The default is scipy.stats.poisson. user_growth_dist_parameters : Union[List,Dict], optional Either a list of dictionaries, or a dictionary. If a list is provided then the parameters change at every iteration. The default is [{'mu':1000}]. add_to_user_base : bool, optional If False, then the actual number of users is sampled from the distribution. If True, then this number of users is added to the total number of users. Default is false. num_initial_users : int, optional The number of initial users. Default is 0 """ super(UserGrowth_Stochastic,self).__init__() self.user_growth_distribution=user_growth_distribution self.user_growth_dist_parameters=user_growth_dist_parameters self.num_users=num_initial_users self.add_to_userbase=add_to_userbase return None def execute(self): seed=int(int(time.time())*np.random.rand()) if type(self.user_growth_dist_parameters)==list: try: params=self.user_growth_dist_parameters[self.iteration] except: warnings.warn("Iterations for UserGrowth stochastic exhausted. Simply using the last item on the list of parameters.") params=self.user_growth_dist_parameters[-1] else: params=self.user_growth_dist_parameters self._active_params=params value=self.user_growth_distribution.rvs(size=1,random_state=seed,**params)[0] if self.add_to_userbase: self.num_users+=value else: self.num_users=value self.iteration+=1 return self.num_users
Ancestors
- UserGrowth
- baseclasses.Controller
Methods
def execute(self)
-
Expand source code
def execute(self): seed=int(int(time.time())*np.random.rand()) if type(self.user_growth_dist_parameters)==list: try: params=self.user_growth_dist_parameters[self.iteration] except: warnings.warn("Iterations for UserGrowth stochastic exhausted. Simply using the last item on the list of parameters.") params=self.user_growth_dist_parameters[-1] else: params=self.user_growth_dist_parameters self._active_params=params value=self.user_growth_distribution.rvs(size=1,random_state=seed,**params)[0] if self.add_to_userbase: self.num_users+=value else: self.num_users=value self.iteration+=1 return self.num_users
Inherited members