Coverage for .tox/p312/lib/python3.10/site-packages/scicom/knowledgespread/agents.py: 0%
71 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-05-28 12:02 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2024-05-28 12:02 +0200
2import mesa
3import networkx as nx
5from scicom.knowledgespread.utils import ageFunction, epistemicRange
8class ScientistAgent(mesa.Agent):
9 """A scientist with an idea.
11 Each scientist has a geographic position, is related to other
12 agents by a social network, and is intialized with a starttime, that
13 describes the year at which the agent becomes active.
14 """
16 def __init__(
17 self,
18 unique_id,
19 model,
20 pos: tuple, # The projected position in 2D epistemic space. For actual movements and neighborhood calculations take into account z coordinate as well.
21 topicledger: list, # Representing the mental model of the agent: A list of all visited topics represented as triples [(x,y,z), (x,y,z)]
22 geopos: tuple, # Representing scientific affiliation, a tuple of latitude/longitude of current affiliation. Could also keep track of previous affiliations
23 birthtime: int, # A step number that represents the timestep at which the scientist becomes active
24 productivity: tuple, # Parameters determining the shape of the activation weight function
25 opposition: bool = False, # Whether or not an agent is always oposing new epistemic positions.
26 ):
27 super().__init__(unique_id, model)
28 self.pos = pos
29 self.a = productivity[0]
30 self.b = productivity[1]
31 self.c = productivity[2]
32 self.topicledger = topicledger
33 self.geopos = geopos
34 self.birthtime = birthtime
35 self.age = 0
36 self.opposition = opposition
38 def _currentActivationWeight(self) -> float:
39 """Return an age dependent activation weight.
41 A bell-shaped function with a ramp, plateuax and decend.
42 Can be drawn from random distribution in model initialization.
43 """
44 return ageFunction(self, self.a, self.b, self.c, radius=1)
46 def _changeEpistemicPosition(self, neighbors):
47 """Calculate the change in epistemic space.
49 From all neighbors select one random choice.
50 To update the agents position, determine the heading
51 towards the selected neighbor. If the agent is an
52 oposing one, inverte the direction. Then select a
53 random amount to move into the selected direction.
54 The new position is noted down in the topic ledger
55 an the the agent is moved.
56 """
57 # Select random elemt from potential neighbors
58 neighborID = self.random.choice(neighbors)
59 if isinstance(neighborID, (float, int)):
60 neighbors = [
61 x for x in self.model.schedule.agents if x.unique_id == neighborID
62 ]
63 if neighbors:
64 neighbor = neighbors[0]
65 else:
66 return
67 else:
68 neighbor = neighborID
69 # Get heading
70 direction = self.model.space.get_heading(self.pos, neighbor.pos)
71 # Some agents always opose the epistemic position and therefore move in the oposite direction
72 if self.opposition is True:
73 direction = (- direction[0], - direction[1])
74 # Select new postion with random amount into direction of neighbor
75 amount = self.model.random.random()
76 new_pos = (self.pos[0] + amount * direction[0], self.pos[1] + amount * direction[1])
77 # New mental position
78 topic = (new_pos[0], new_pos[1], self.model.random.random())
79 try:
80 # Move agent
81 self.topicledger.append(topic)
82 self.model.space.move_agent(self, new_pos)
83 except:
84 # Out of bounds of epi space
85 # TODO: What is a sensible exception of movement in this case.
86 # Current solution: Append topic to ledger but do not move
87 self.topicledger.append(topic)
89 def updateSocialNetwork(self):
90 """Create new links in agents social network."""
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 else:
152 self.age += 1
153 currentActivation = self.model.random.choices(
154 population=[0, 1],
155 weights=[
156 1 - self._currentActivationWeight(), self._currentActivationWeight(),
157 ],
158 k=1,
159 )
160 if currentActivation[0] == 1:
161 # TODO: Should the choice of movement be another random process?
162 res = self.moveSocSpace()
163 if res is False:
164 self.moveEpiSpace()