Coverage for .tox/p311/lib/python3.10/site-packages/scicom/knowledgespread/model.py: 0%

71 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-25 12:40 +0200

1import networkx as nx 

2import mesa 

3from scicom.knowledgespread.agents import ScientistAgent 

4from scicom.knowledgespread.utils import GenerateInitalPopulation, GenerateSocNet 

5 

6 

7def getActiveAgents(model): 

8 """Get all agents active at time t.""" 

9 active = 0 

10 for x in model.schedule.agents: 

11 if x.birthtime <= model.schedule.time: 

12 active += 1 

13 return active 

14 

15 

16def getNetworkStructure(model): 

17 return [len(x) for x in nx.community.louvain_communities(model.socialNetwork)] 

18 

19 

20class KnowledgeSpread(mesa.Model): 

21 """A model for knowledge spread.  

22  

23 Agents have an initial topic vector and are positioned in epistemic space.  

24 The number of agents can grow linearly, as a s-curve, or exponentially. 

25 Agents initial positions on epistemic space can be diverse (checker board-like), 

26 around a central position or in opossing camps.  

27 

28 Agents activation probability is age-dependent. After reaching a personal productivity end, 

29 agents are removed from the scheduler. 

30 

31 """ 

32 

33 def __init__( 

34 self, 

35 num_scientists: int = 100, 

36 num_timesteps: int = 20, 

37 epiDim: float = 1.0001, # This allows the boundary to be equal +/- one, will raise an exception otherwise 

38 epiRange: float = 0.01, # Range of visibility in epistemic space. 

39 oppositionPercent: float = 0.05, # Weight for random draw of opossing agents  

40 loadInitialConditions: bool = False, 

41 epiInit: str = "complex", 

42 timeInit: str = "saturate", 

43 beta: int = 8, 

44 slope: int = 5, 

45 base: int = 2, 

46 ): 

47 

48 self.numScientists = num_scientists 

49 self.numTimesteps = num_timesteps 

50 self.epiRange = epiRange 

51 self.opposPercent = oppositionPercent 

52 self.loadInitialConditions = loadInitialConditions 

53 self.epiInit = epiInit 

54 self.timeInit = timeInit 

55 self.beta = beta 

56 self.slope = slope 

57 self.base = base 

58 

59 # Random Schedule 

60 self.schedule = mesa.time.RandomActivation(self) 

61 

62 # Epistemic layer space  

63 self.space = mesa.space.ContinuousSpace( 

64 epiDim, epiDim, False, -epiDim, -epiDim 

65 ) 

66 

67 # Create initial setup of agents 

68 # TODO: The topic vector could be a higher dimensional vector derived from text embeddings. 

69 self._setupAgents() 

70 

71 # Create agents from initial conditions.  

72 agents = [] 

73 for ix, cond in self.initpop.iterrows(): 

74 opose = self.random.choices([False, True], weights=[1 - self.opposPercent, self.opposPercent], k=1) 

75 # TODO: The distribution of productivity length could be empirically motivated.  

76 prodlen = self.random.choices(list(range(15, 55, 1)), k=1) 

77 agent = ScientistAgent( 

78 unique_id=cond["id"], 

79 model=self, 

80 pos=(cond["x"], cond["y"]), 

81 topicledger=[(cond["x"], cond["y"], cond["z"])], 

82 geopos=(45, 45), 

83 birthtime=cond["t"], 

84 opposition=opose[0], 

85 productivity=(7, 7, prodlen[0]) 

86 ) 

87 agents.append(agent) 

88 

89 # Setup social layer connections and space 

90 edges = self._setupSocialSpace() 

91 self.socialNetwork = nx.from_pandas_edgelist( 

92 edges, 

93 source='from_id', 

94 target='to_id', 

95 edge_attr=["time", "dist"] 

96 ) 

97 # TODO: What is the effect of the GRID dimensions on social dynamics? 

98 self.grid = mesa.space.MultiGrid(1000, 1000, torus=False) 

99 

100 # Add agents to epistemic space and schedule. 

101 for agent in agents: 

102 self.space.place_agent( 

103 agent, pos=agent.pos 

104 ) 

105 x = self.random.randrange(self.grid.width) 

106 y = self.random.randrange(self.grid.height) 

107 self.grid.place_agent( 

108 agent, (x, y) 

109 ) 

110 self.schedule.add(agent) 

111 

112 # Setup data collection 

113 self.datacollector = mesa.DataCollector( 

114 model_reporters={ 

115 "Active Agents": lambda m: getActiveAgents(m), 

116 "Graph structure": lambda m: getNetworkStructure(m), 

117 }, 

118 ) 

119 

120 # Start in running mode 

121 self.running = True 

122 

123 def _setupAgents(self): 

124 """Create initial setup of agents.""" 

125 if self.loadInitialConditions is False: 

126 epiInit = self.epiInit 

127 timeInit = self.timeInit 

128 beta = self.beta 

129 slope = self.slope 

130 base = self.base 

131 else: 

132 raise NotImplementedError("The reading-in of external initial conditions is not implemented yet. Come back later!") 

133 epiOpt = ["complex", "central", "polarized"] 

134 timeOpt = ["saturate", "linear", "exponential"] 

135 if epiInit in epiOpt and timeInit in timeOpt: 

136 generate = GenerateInitalPopulation(self.numScientists, self.numTimesteps) 

137 initalPop = generate.sample( 

138 fcE=epiInit, 

139 fcT=timeInit, 

140 beta=beta, 

141 slope=slope, 

142 base=base 

143 ) 

144 self.initpop = initalPop 

145 else: 

146 raise KeyError(f"Choose epiInit from {epiOpt} and timeInit from {timeOpt}.") 

147 

148 def _setupSocialSpace(self, nEdges=4, density=0.2, densityGrowth=0): 

149 """Setup initial social connections.""" 

150 genSoc = GenerateSocNet(self.initpop) 

151 socNet = genSoc.run( 

152 nEdges=nEdges, 

153 density=density, 

154 densityGrowth=densityGrowth 

155 ) 

156 return socNet 

157 

158 def step(self): 

159 """Run one simulation step.""" 

160 if not len(self.schedule.agents) > 1: 

161 self.running = False 

162 else: 

163 self.schedule.step() 

164 self.datacollector.collect(self) 

165 

166 def run(self, n): 

167 """Run model for n steps.""" 

168 for _ in range(n): 

169 self.step()