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

201 rows × 2 columns

stepcount_stabilized
Int64Int64
100
2174
3274
4377
5481
6579
7675
8771
9874
10976
111077
121174
131274
141377
151465
161567
171662
181774
191875
201975
212068
222177
232278
242377
252469
262571
272670
282771
292870
302972
313076
323174
333261
343379
353472
363572
373674
383768
393878
403977
414069
424164
434274
444372
454470
464570
474675
484774
494868
504977
515074
525180
535271
545378
555473
565580
575678
585768
595878
605972
616076
626173
636259
646361
656460
666561
676661
686767
696859
706963
717073
727164
737257
747364
757478
767565
777668
787773
797865
807975
818064
828172
838273
848368
858466
868567
878665
888765
898861
908963
919064
929160
939272
949364
959465
969569
979661
989769
999861
1009972
10110075
10210175
10310265
10410361
10510475
10610563
10710672
10810767
10910859
11010972
11111071
11211175
11311271
11411372
11511479
11611577
11711671
11811783
11911874
12011979
12112081
12212177
12312284
12412379
12512478
12612572
12712671
12812782
12912878
13012975
13113079
13213180
13313269
13413368
13513464
13613568
13713677
13813775
13913869
14013975
14114073
14214165
14314275
14414372
14514472
14614564
14714668
14814780
14914876
15014979
15115075
15215175
15315272
15415375
15515475
15615570
15715677
15815775
15915875
16015979
16116074
16216172
16316281
16416373
16516475
16616584
16716680
16816792
16916891
17016988
17117090
17217188
17317292
17417387
17517489
17617594
17717687
17817787
17917889
18017981
18118083
18218182
18318283
18418385
18518479
18618581
18718687
18818791
18918888
19018987
19119091
19219192
19319291
19419395
19519496
19619589
19719688
19819796
19919890
20019997
201200100

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")