Coverage for src/scicom/knowledgespread/agents.py: 0%
73 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-15 13:26 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-15 13:26 +0200
2from scicom.knowledgespread.utils import epistemicRange, ageFunction
3import networkx as nx
4import mesa
7class ScientistAgent(mesa.Agent):
8 """A scientist with an idea.
10 Each scientist has a geographic position, is related to other
11 agents by a social network, and is intialized with a starttime, that
12 describes the year at which the agent becomes active.
13 """
15 def __init__(
16 self,
17 unique_id,
18 model,
19 pos: tuple, # The projected position in 2D epistemic space. For actual movements and neighborhood calculations take into account z coordinate as well.
20 topicledger: list, # Representing the mental model of the agent: A list of all visited topics represented as triples [(x,y,z), (x,y,z)]
21 geopos: tuple, # Representing scientific affiliation, a tuple of latitude/longitude of current affiliation. Could also keep track of previous affiliations
22 birthtime: int, # A step number that represents the timestep at which the scientist becomes active
23 productivity: tuple, # Parameters determining the shape of the activation weight function
24 opposition: bool = False, # Whether or not an agent is always oposing new epistemic positions.
25 ):
26 super().__init__(unique_id, model)
27 self.pos = pos
28 self.a = productivity[0]
29 self.b = productivity[1]
30 self.c = productivity[2]
31 self.topicledger = topicledger
32 self.geopos = geopos
33 self.birthtime = birthtime
34 self.age = 0
35 self.opposition = opposition
37 def _currentActivationWeight(self):
38 """Returns an age dependent activation weight.
40 A bell-shaped function with a ramp, plateuax and decend.
41 Can be drawn from random distribution in model initialization.
42 """
43 return ageFunction(self, self.a, self.b, self.c, radius=1)
45 def _changeEpistemicPosition(self, neighbors):
46 """Calculate the change in epistemic space.
48 From all neighbors select one random choice.
49 To update the agents position, determine the heading
50 towards the selected neighbor. If the agent is an
51 oposing one, inverte the direction. Then select a
52 random amount to move into the selected direction.
53 The new position is noted down in the topic ledger
54 an the the agent is moved.
55 """
56 # Select random elemt from potential neighbors
57 neighborID = self.random.choice(neighbors)
58 if isinstance(neighborID, float) or isinstance(neighborID, int):
59 neighbors = [
60 x for x in self.model.schedule.agents if x.unique_id == neighborID
61 ]
62 if neighbors:
63 neighbor = neighbors[0]
64 else:
65 return
66 else:
67 neighbor = neighborID
68 # Get heading
69 direction = self.model.space.get_heading(self.pos, neighbor.pos)
70 # Some agents always opose the epistemic position and therefore move in the oposite direction
71 if self.opposition is True:
72 direction = (- direction[0], - direction[1])
73 # Select new postion with random amount into direction of neighbor
74 amount = self.model.random.random()
75 new_pos = (self.pos[0] + amount * direction[0], self.pos[1] + amount * direction[1])
76 # New mental position
77 topic = (new_pos[0], new_pos[1], self.model.random.random())
78 try:
79 # Move agent
80 self.topicledger.append(topic)
81 self.model.space.move_agent(self, new_pos)
82 except:
83 # Out of bounds of epi space
84 # TODO: What is a sensible exception of movement in this case.
85 # Current solution: Append topic to ledger but do not move
86 self.topicledger.append(topic)
88 def updateSocialNetwork(self):
89 """Create new links in agents social network."""
90 pass
92 def moveGeoSpace(self):
93 pass
95 def moveSocSpace(self, maxDist=1):
96 """Change epistemic position based on social network."""
97 neighbors = []
98 currentTime = self.model.schedule.time
99 G = self.model.socialNetwork
100 H = nx.Graph(((u, v, e) for u, v, e in G.edges(data=True) if e['time'] <= currentTime))
101 for dist in range(0, maxDist + 1, 1):
102 neighb = nx.descendants_at_distance(
103 H,
104 self.unique_id,
105 dist
106 )
107 neighbors.extend(list(neighb))
108 if neighbors:
109 self._changeEpistemicPosition(neighbors)
110 return True
111 else:
112 return False
114 def moveEpiSpace(self):
115 """Change epistemic position based on distance in epistemic space."""
116 neighbors = self.model.space.get_neighbors(
117 self.pos,
118 radius=epistemicRange(
119 self.model.epiRange,
120 self.model.schedule.time - self.birthtime
121 )
122 )
123 if neighbors:
124 self._changeEpistemicPosition(neighbors)
125 else:
126 # Random search for new epistemic position
127 direction = (self.model.random.random(), self.model.random.random())
128 new_pos = self.model.random.random() * direction
129 self.model.space.move_agent(self, new_pos)
131 def attendConference(self):
132 pass
134 def step(self):
135 """Agents activity starts after having reached the birthtime.
137 After initial start, at each step the agents age is increased by one.
138 Each agent has a randomly generated age-dependent activation
139 probability. Moveing happens first due to social connections. If
140 no move due to social connections was possible, a move due to
141 epistemic space search is attempted.
143 Once the possible activation weight drops below a threshold,
144 the agent is removed from the schedule.
145 """
146 if self.model.schedule.time < self.birthtime:
147 pass
148 elif self._currentActivationWeight() <= 0.00001 and self.age > 1:
149 self.model.schedule.remove(self)
150 # self.age += 1
151 pass
152 else:
153 self.age += 1
154 currentActivation = self.model.random.choices(
155 population=[0, 1],
156 weights=[
157 1 - self._currentActivationWeight(), self._currentActivationWeight()
158 ],
159 k=1
160 )
161 if currentActivation[0] == 1:
162 # TODO: Should the choice of movement be another random process?
163 res = self.moveSocSpace()
164 if res is False:
165 self.moveEpiSpace()