Opinion spread
This is a simple model of how an opinion spreads through a community. Each individual has a number of opinions as a list of integers. They can change their opinion by changing the numbers in the list.
Agents can change their opinion at each step. They choose one of their neighbors randomly, and adopt one of the neighbor's opinion. They are more likely to adopt their neighbors opinion if the share more opinions with each other.
using Agents
using Plots
using Random
Building the model
1. Model creation
mutable struct Citizen <: AbstractAgent
id::Int
pos::Dims{2}
stabilized::Bool
opinion::Array{Int,1}
prev_opinion::Array{Int,1}
end
function create_model(; dims = (10, 10), nopinions = 3, levels_per_opinion = 4)
space = GridSpace(dims)
properties = Dict(:nopinions => nopinions)
model = AgentBasedModel(
Citizen,
space,
scheduler = random_activation,
properties = properties,
)
for pos in positions(model)
add_agent!(
pos,
model,
false,
rand(1:levels_per_opinion, nopinions),
rand(1:levels_per_opinion, nopinions),
)
end
return model
end
create_model (generic function with 1 method)
2. Stepping functions
function adopt!(agent, model)
neighbor = rand(collect(nearby_ids(agent, model)))
matches = model[neighbor].opinion .== agent.opinion
nmatches = count(matches)
if nmatches < model.nopinions && rand() < nmatches / model.nopinions
switchId = rand(findall(x -> x == false, matches))
agent.opinion[switchId] = model[neighbor].opinion[switchId]
end
end
function update_prev_opinion!(agent, model)
for i in 1:(model.nopinions)
agent.prev_opinion[i] = agent.opinion[i]
end
end
function is_stabilized!(agent, model)
if agent.prev_opinion == agent.opinion
agent.stabilized = true
else
agent.stabilized = false
end
end
function agent_step!(agent, model)
update_prev_opinion!(agent, model)
adopt!(agent, model)
is_stabilized!(agent, model)
end
agent_step! (generic function with 1 method)
Running the model
First, we create a stopping condition, which runs the model until all agents stabilize.
rununtil(model, s) = count(a->a.stabilized, allagents(model)) == length(positions(model))
rununtil (generic function with 1 method)
Then we create our model, run it and collect some information
model = create_model(nopinions = 3, levels_per_opinion = 4)
agentdata, _ = run!(model, agent_step!, dummystep, rununtil, adata = [(:stabilized, count)])
agentdata
step | count_stabilized | |
---|---|---|
Int64 | Int64 | |
1 | 0 | 0 |
2 | 1 | 74 |
3 | 2 | 74 |
4 | 3 | 77 |
5 | 4 | 81 |
6 | 5 | 79 |
7 | 6 | 75 |
8 | 7 | 71 |
9 | 8 | 74 |
10 | 9 | 76 |
11 | 10 | 77 |
12 | 11 | 74 |
13 | 12 | 74 |
14 | 13 | 77 |
15 | 14 | 65 |
16 | 15 | 67 |
17 | 16 | 62 |
18 | 17 | 74 |
19 | 18 | 75 |
20 | 19 | 75 |
21 | 20 | 68 |
22 | 21 | 77 |
23 | 22 | 78 |
24 | 23 | 77 |
25 | 24 | 69 |
26 | 25 | 71 |
27 | 26 | 70 |
28 | 27 | 71 |
29 | 28 | 70 |
30 | 29 | 72 |
31 | 30 | 76 |
32 | 31 | 74 |
33 | 32 | 61 |
34 | 33 | 79 |
35 | 34 | 72 |
36 | 35 | 72 |
37 | 36 | 74 |
38 | 37 | 68 |
39 | 38 | 78 |
40 | 39 | 77 |
41 | 40 | 69 |
42 | 41 | 64 |
43 | 42 | 74 |
44 | 43 | 72 |
45 | 44 | 70 |
46 | 45 | 70 |
47 | 46 | 75 |
48 | 47 | 74 |
49 | 48 | 68 |
50 | 49 | 77 |
51 | 50 | 74 |
52 | 51 | 80 |
53 | 52 | 71 |
54 | 53 | 78 |
55 | 54 | 73 |
56 | 55 | 80 |
57 | 56 | 78 |
58 | 57 | 68 |
59 | 58 | 78 |
60 | 59 | 72 |
61 | 60 | 76 |
62 | 61 | 73 |
63 | 62 | 59 |
64 | 63 | 61 |
65 | 64 | 60 |
66 | 65 | 61 |
67 | 66 | 61 |
68 | 67 | 67 |
69 | 68 | 59 |
70 | 69 | 63 |
71 | 70 | 73 |
72 | 71 | 64 |
73 | 72 | 57 |
74 | 73 | 64 |
75 | 74 | 78 |
76 | 75 | 65 |
77 | 76 | 68 |
78 | 77 | 73 |
79 | 78 | 65 |
80 | 79 | 75 |
81 | 80 | 64 |
82 | 81 | 72 |
83 | 82 | 73 |
84 | 83 | 68 |
85 | 84 | 66 |
86 | 85 | 67 |
87 | 86 | 65 |
88 | 87 | 65 |
89 | 88 | 61 |
90 | 89 | 63 |
91 | 90 | 64 |
92 | 91 | 60 |
93 | 92 | 72 |
94 | 93 | 64 |
95 | 94 | 65 |
96 | 95 | 69 |
97 | 96 | 61 |
98 | 97 | 69 |
99 | 98 | 61 |
100 | 99 | 72 |
101 | 100 | 75 |
102 | 101 | 75 |
103 | 102 | 65 |
104 | 103 | 61 |
105 | 104 | 75 |
106 | 105 | 63 |
107 | 106 | 72 |
108 | 107 | 67 |
109 | 108 | 59 |
110 | 109 | 72 |
111 | 110 | 71 |
112 | 111 | 75 |
113 | 112 | 71 |
114 | 113 | 72 |
115 | 114 | 79 |
116 | 115 | 77 |
117 | 116 | 71 |
118 | 117 | 83 |
119 | 118 | 74 |
120 | 119 | 79 |
121 | 120 | 81 |
122 | 121 | 77 |
123 | 122 | 84 |
124 | 123 | 79 |
125 | 124 | 78 |
126 | 125 | 72 |
127 | 126 | 71 |
128 | 127 | 82 |
129 | 128 | 78 |
130 | 129 | 75 |
131 | 130 | 79 |
132 | 131 | 80 |
133 | 132 | 69 |
134 | 133 | 68 |
135 | 134 | 64 |
136 | 135 | 68 |
137 | 136 | 77 |
138 | 137 | 75 |
139 | 138 | 69 |
140 | 139 | 75 |
141 | 140 | 73 |
142 | 141 | 65 |
143 | 142 | 75 |
144 | 143 | 72 |
145 | 144 | 72 |
146 | 145 | 64 |
147 | 146 | 68 |
148 | 147 | 80 |
149 | 148 | 76 |
150 | 149 | 79 |
151 | 150 | 75 |
152 | 151 | 75 |
153 | 152 | 72 |
154 | 153 | 75 |
155 | 154 | 75 |
156 | 155 | 70 |
157 | 156 | 77 |
158 | 157 | 75 |
159 | 158 | 75 |
160 | 159 | 79 |
161 | 160 | 74 |
162 | 161 | 72 |
163 | 162 | 81 |
164 | 163 | 73 |
165 | 164 | 75 |
166 | 165 | 84 |
167 | 166 | 80 |
168 | 167 | 92 |
169 | 168 | 91 |
170 | 169 | 88 |
171 | 170 | 90 |
172 | 171 | 88 |
173 | 172 | 92 |
174 | 173 | 87 |
175 | 174 | 89 |
176 | 175 | 94 |
177 | 176 | 87 |
178 | 177 | 87 |
179 | 178 | 89 |
180 | 179 | 81 |
181 | 180 | 83 |
182 | 181 | 82 |
183 | 182 | 83 |
184 | 183 | 85 |
185 | 184 | 79 |
186 | 185 | 81 |
187 | 186 | 87 |
188 | 187 | 91 |
189 | 188 | 88 |
190 | 189 | 87 |
191 | 190 | 91 |
192 | 191 | 92 |
193 | 192 | 91 |
194 | 193 | 95 |
195 | 194 | 96 |
196 | 195 | 89 |
197 | 196 | 88 |
198 | 197 | 96 |
199 | 198 | 90 |
200 | 199 | 97 |
201 | 200 | 100 |
Plotting
The plot shows the number of stable agents, that is, number of agents whose opinions don't change from one step to the next. Note that the number of stable agents can fluctuate before the final convergence.
plot(
1:size(agentdata, 1),
agentdata.count_stabilized,
legend = false,
xlabel = "generation",
ylabel = "# of stabilized agents",
)
Animation
Here is an animation that shows change of agent opinions over time. The first three opinions of an agent determines its color in RGB.
levels_per_opinion = 3
ac(agent) = RGB((agent.opinion[1:3] ./ levels_per_opinion)...)
model = create_model(nopinions = 3, levels_per_opinion = levels_per_opinion)
anim = @animate for sp in 1:500
step!(model, agent_step!)
p = plotabm(model, ac = ac, as = 12, am = :square)
title!(p, "Step $(sp)")
if rununtil(model, 1)
break
end
end
gif(anim, "opinion.gif")