diff --git a/benchmark/agents.jl b/benchmark/agents.jl index 9c8d96a56c..bb5fb15cd5 100644 --- a/benchmark/agents.jl +++ b/benchmark/agents.jl @@ -83,4 +83,3 @@ mutable struct ContinuousAgentFive <: AbstractAgent pos::NTuple{3,Float64} vel::NTuple{3,Float64} end - diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 2fcde2fad7..ad8965744c 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -64,30 +64,37 @@ graph_model = ABM(GraphAgent, GraphSpace(complete_digraph(200))) graph_agent = GraphAgent(1, 82, 6.5, false) graph_union_model = ABM( Union{GraphAgent,GraphAgentTwo,GraphAgentThree,GraphAgentFour,GraphAgentFive}, - GraphSpace(complete_digraph(200)), - warn = false, + GraphSpace(complete_digraph(200)); + warn=false, ) # Limit samples here so space does not saturate with agents -SUITE["graph"]["add"]["agent"] = - @benchmarkable add_agent!($graph_agent, $graph_model) samples = 100 -SUITE["graph"]["add"]["agent_pos"] = - @benchmarkable add_agent_pos!($graph_agent, $graph_model) samples = 100 -SUITE["graph"]["add"]["agent_single"] = - @benchmarkable add_agent_single!($graph_agent, $graph_model) samples = 100 -SUITE["graph"]["add"]["create_pos"] = - @benchmarkable add_agent!(26, $graph_model, 6.5, false) samples = 100 -SUITE["graph"]["add"]["create_single"] = - @benchmarkable add_agent_single!($graph_model, 6.5, false) samples = 100 -SUITE["graph"]["add"]["create"] = - @benchmarkable add_agent!($graph_model, 6.5, false) samples = 100 - -SUITE["graph"]["add_union"]["agent"] = - @benchmarkable add_agent!($graph_agent, $graph_union_model) samples = 100 -SUITE["graph"]["add_union"]["agent_pos"] = - @benchmarkable add_agent_pos!($graph_agent, $graph_union_model) samples = 100 -SUITE["graph"]["add_union"]["agent_single"] = - @benchmarkable add_agent_single!($graph_agent, $graph_union_model) samples = 100 +SUITE["graph"]["add"]["agent"] = @benchmarkable add_agent!($graph_agent, $graph_model) samples = + 100 +SUITE["graph"]["add"]["agent_pos"] = @benchmarkable add_agent_pos!( + $graph_agent, $graph_model +) samples = 100 +SUITE["graph"]["add"]["agent_single"] = @benchmarkable add_agent_single!( + $graph_agent, $graph_model +) samples = 100 +SUITE["graph"]["add"]["create_pos"] = @benchmarkable add_agent!( + 26, $graph_model, 6.5, false +) samples = 100 +SUITE["graph"]["add"]["create_single"] = @benchmarkable add_agent_single!( + $graph_model, 6.5, false +) samples = 100 +SUITE["graph"]["add"]["create"] = @benchmarkable add_agent!($graph_model, 6.5, false) samples = + 100 + +SUITE["graph"]["add_union"]["agent"] = @benchmarkable add_agent!( + $graph_agent, $graph_union_model +) samples = 100 +SUITE["graph"]["add_union"]["agent_pos"] = @benchmarkable add_agent_pos!( + $graph_agent, $graph_union_model +) samples = 100 +SUITE["graph"]["add_union"]["agent_single"] = @benchmarkable add_agent_single!( + $graph_agent, $graph_union_model +) samples = 100 graph_model = ABM(GraphAgent, GraphSpace(complete_digraph(100))) for position in 1:100 @@ -102,20 +109,24 @@ SUITE["graph"]["move"]["pos"] = @benchmarkable move_agent!($a, 68, $graph_model) SUITE["graph"]["move"]["single"] = @benchmarkable move_agent_single!($a, $graph_model) # We use a digraph, so all agents are neighbors of each other -SUITE["graph"]["neighbors"]["nearby_ids"] = - @benchmarkable nearby_ids($pos, $graph_model) setup = (nearby_ids($pos, $graph_model)) -SUITE["graph"]["neighbors"]["nearby_agents"] = - @benchmarkable nearby_ids($a, $graph_model) setup = (nearby_ids($a, $graph_model)) -SUITE["graph"]["neighbors"]["nearby_ids_iterate"] = - @benchmarkable iterate_over_neighbors($pos, $graph_model, 1) setup = - (nearby_ids($pos, $graph_model)) -SUITE["graph"]["neighbors"]["nearby_agents_iterate"] = - @benchmarkable iterate_over_neighbors($a, $graph_model, 1) setup = - (nearby_ids($a, $graph_model)) -SUITE["graph"]["neighbors"]["position_pos"] = - @benchmarkable nearby_positions($pos, $graph_model) -SUITE["graph"]["neighbors"]["position_agent"] = - @benchmarkable nearby_positions($a, $graph_model) +SUITE["graph"]["neighbors"]["nearby_ids"] = @benchmarkable nearby_ids($pos, $graph_model) setup = (nearby_ids( + $pos, $graph_model +)) +SUITE["graph"]["neighbors"]["nearby_agents"] = @benchmarkable nearby_ids($a, $graph_model) setup = (nearby_ids( + $a, $graph_model +)) +SUITE["graph"]["neighbors"]["nearby_ids_iterate"] = @benchmarkable iterate_over_neighbors( + $pos, $graph_model, 1 +) setup = (nearby_ids($pos, $graph_model)) +SUITE["graph"]["neighbors"]["nearby_agents_iterate"] = @benchmarkable iterate_over_neighbors( + $a, $graph_model, 1 +) setup = (nearby_ids($a, $graph_model)) +SUITE["graph"]["neighbors"]["position_pos"] = @benchmarkable nearby_positions( + $pos, $graph_model +) +SUITE["graph"]["neighbors"]["position_agent"] = @benchmarkable nearby_positions( + $a, $graph_model +) SUITE["graph"]["position"]["contents"] = @benchmarkable ids_in_position($pos, $graph_model) SUITE["graph"]["position"]["positions"] = @benchmarkable positions($graph_model) @@ -127,18 +138,20 @@ grid_agent = GridAgent(1, (2, 3), 6.5, false) grid_union_model = ABM( Union{GridAgent,GridAgentTwo,GridAgentThree,GridAgentFour,GridAgentFive}, GridSpace((15, 15)); - warn = false, + warn=false, ) -SUITE["grid"]["add"]["agent_pos"] = - @benchmarkable add_agent_pos!($grid_agent, $grid_model) samples = 100 -SUITE["grid"]["add"]["create_fill"] = - @benchmarkable fill_space!($grid_model, 6.5, false) samples = 100 +SUITE["grid"]["add"]["agent_pos"] = @benchmarkable add_agent_pos!($grid_agent, $grid_model) samples = + 100 +SUITE["grid"]["add"]["create_fill"] = @benchmarkable fill_space!($grid_model, 6.5, false) samples = + 100 -SUITE["grid"]["add_union"]["agent_pos"] = - @benchmarkable add_agent_pos!($grid_agent, $grid_union_model) samples = 100 -SUITE["grid"]["add_union"]["agent_fill"] = - @benchmarkable fill_space!(GridAgent, $grid_union_model, 6.5, false) samples = 100 +SUITE["grid"]["add_union"]["agent_pos"] = @benchmarkable add_agent_pos!( + $grid_agent, $grid_union_model +) samples = 100 +SUITE["grid"]["add_union"]["agent_fill"] = @benchmarkable fill_space!( + GridAgent, $grid_union_model, 6.5, false +) samples = 100 grid_model = ABM(GridAgent, GridSpace((50, 50))) for x in 1:50 @@ -154,58 +167,62 @@ SUITE["grid"]["move"]["random"] = @benchmarkable move_agent!($a, $grid_model) SUITE["grid"]["move"]["pos"] = @benchmarkable move_agent!($a, (14, 35), $grid_model) SUITE["grid"]["move"]["single"] = @benchmarkable move_agent_single!($a, $grid_model) -SUITE["grid"]["neighbors"]["nearby_ids"] = - @benchmarkable nearby_ids($pos, $grid_model, 5) setup = - (nearby_ids($pos, $grid_model, 5)) -SUITE["grid"]["neighbors"]["nearby_agents"] = - @benchmarkable nearby_ids($a, $grid_model, 5) setup = (nearby_ids($a, $grid_model, 5)) +SUITE["grid"]["neighbors"]["nearby_ids"] = @benchmarkable nearby_ids($pos, $grid_model, 5) setup = (nearby_ids( + $pos, $grid_model, 5 +)) +SUITE["grid"]["neighbors"]["nearby_agents"] = @benchmarkable nearby_ids($a, $grid_model, 5) setup = (nearby_ids( + $a, $grid_model, 5 +)) -SUITE["grid"]["neighbors"]["nearby_ids_iterate"] = - @benchmarkable iterate_over_neighbors($pos, $grid_model, 30) setup = - (nearby_ids($pos, $grid_model, 30)) +SUITE["grid"]["neighbors"]["nearby_ids_iterate"] = @benchmarkable iterate_over_neighbors( + $pos, $grid_model, 30 +) setup = (nearby_ids($pos, $grid_model, 30)) -SUITE["grid"]["neighbors"]["nearby_agents_iterate"] = - @benchmarkable iterate_over_neighbors($a, $grid_model, 30) setup = - (nearby_ids($a, $grid_model, 30)) +SUITE["grid"]["neighbors"]["nearby_agents_iterate"] = @benchmarkable iterate_over_neighbors( + $a, $grid_model, 30 +) setup = (nearby_ids($a, $grid_model, 30)) -SUITE["grid"]["neighbors"]["position_pos"] = - @benchmarkable nearby_positions($a, $grid_model) -SUITE["grid"]["neighbors"]["position_agent"] = - @benchmarkable nearby_positions($a, $grid_model) +SUITE["grid"]["neighbors"]["position_pos"] = @benchmarkable nearby_positions( + $a, $grid_model +) +SUITE["grid"]["neighbors"]["position_agent"] = @benchmarkable nearby_positions( + $a, $grid_model +) SUITE["grid"]["position"]["contents"] = @benchmarkable ids_in_position($pos, $grid_model) SUITE["graph"]["position"]["positions"] = @benchmarkable positions($graph_model) #### API -> CONTINUOUS #### -continuous_model = ABM(ContinuousAgent, ContinuousSpace((10.0, 10.0, 10.0); spacing = 0.5)) +continuous_model = ABM(ContinuousAgent, ContinuousSpace((10.0, 10.0, 10.0); spacing=0.5)) continuous_agent = ContinuousAgent(1, (2.2, 1.9, 7.5), (0.5, 1.0, 0.01), 6.5, false) # We must use setup create the model inside some benchmarks here, otherwise we hit the issue from #226. # For tuning, this is actually impossible. So until ContinuousSpace is implemented, we drop these tests. -SUITE["continuous"]["add"]["agent_pos"] = - @benchmarkable add_agent_pos!($continuous_agent, cmodel) setup = - (cmodel = ABM(ContinuousAgent, ContinuousSpace((10.0, 10.0, 10.0); spacing = 0.5))) samples = - 100 - -SUITE["continuous"]["add_union"]["agent_pos"] = - @benchmarkable add_agent_pos!($continuous_agent, cmodel) setup = ( - cmodel = ABM( - Union{ - ContinuousAgent, - ContinuousAgentTwo, - ContinuousAgentThree, - ContinuousAgentFour, - ContinuousAgentFive, - }, - ContinuousSpace((10.0, 10.0, 10.0), spacing = 0.5); - warn = false, - ) - ) samples = 100 - -for x in range(0, stop = 9.99, length = 7) - for y in range(0, stop = 9.99, length = 7) - for z in range(0, stop = 9.99, length = 7) +SUITE["continuous"]["add"]["agent_pos"] = @benchmarkable add_agent_pos!( + $continuous_agent, cmodel +) setup = (cmodel = ABM(ContinuousAgent, ContinuousSpace((10.0, 10.0, 10.0); spacing=0.5))) samples = + 100 + +SUITE["continuous"]["add_union"]["agent_pos"] = @benchmarkable add_agent_pos!( + $continuous_agent, cmodel +) setup = ( + cmodel = ABM( + Union{ + ContinuousAgent, + ContinuousAgentTwo, + ContinuousAgentThree, + ContinuousAgentFour, + ContinuousAgentFive, + }, + ContinuousSpace((10.0, 10.0, 10.0); spacing=0.5); + warn=false, + ) +) samples = 100 + +for x in range(0; stop=9.99, length=7) + for y in range(0; stop=9.99, length=7) + for z in range(0; stop=9.99, length=7) add_agent!((x, y, z), continuous_model, (0.8, 0.7, 1.3), 6.5, false) end end @@ -214,22 +231,23 @@ a = continuous_model[139] pos = (7.07, 8.10, 6.58) SUITE["continuous"]["move"]["update"] = @benchmarkable move_agent!($a, $continuous_model) -SUITE["continuous"]["neighbors"]["nearby_ids"] = - @benchmarkable nearby_ids($pos, $continuous_model, 5) setup = - (nearby_ids($pos, $continuous_model, 5)) - -SUITE["continuous"]["neighbors"]["nearby_agents"] = - @benchmarkable nearby_ids($a, $continuous_model, 5) setup = - (nearby_ids($a, $continuous_model, 5)) - -SUITE["continuous"]["neighbors"]["nearby_ids_iterate"] = - @benchmarkable iterate_over_neighbors($pos, $continuous_model, 10) setup = - (nearby_ids($pos, $continuous_model, 10)) -SUITE["continuous"]["neighbors"]["nearby_agents_iterate"] = - @benchmarkable iterate_over_neighbors($a, $continuous_model, 10) setup = - (nearby_ids($a, $continuous_model, 10)) -SUITE["continuous"]["neighbors"]["nearest"] = - @benchmarkable nearest_neighbor($a, $continuous_model, 5) +SUITE["continuous"]["neighbors"]["nearby_ids"] = @benchmarkable nearby_ids( + $pos, $continuous_model, 5 +) setup = (nearby_ids($pos, $continuous_model, 5)) + +SUITE["continuous"]["neighbors"]["nearby_agents"] = @benchmarkable nearby_ids( + $a, $continuous_model, 5 +) setup = (nearby_ids($a, $continuous_model, 5)) + +SUITE["continuous"]["neighbors"]["nearby_ids_iterate"] = @benchmarkable iterate_over_neighbors( + $pos, $continuous_model, 10 +) setup = (nearby_ids($pos, $continuous_model, 10)) +SUITE["continuous"]["neighbors"]["nearby_agents_iterate"] = @benchmarkable iterate_over_neighbors( + $a, $continuous_model, 10 +) setup = (nearby_ids($a, $continuous_model, 10)) +SUITE["continuous"]["neighbors"]["nearest"] = @benchmarkable nearest_neighbor( + $a, $continuous_model, 5 +) # Benchmark takes too long to be reasonable, even with a small sample. # This needs to be looked at in the future, but it's being ignored for the moment @@ -245,12 +263,15 @@ graph_df = init_agent_dataframe(graph_model, adata) grid_df = init_agent_dataframe(grid_model, adata) continuous_df = init_agent_dataframe(continuous_model, adata) -SUITE["graph"]["collect"]["store_agent"] = - @benchmarkable collect_agent_data!($graph_df, $graph_model, $adata, 0) -SUITE["grid"]["collect"]["store_agent"] = - @benchmarkable collect_agent_data!($grid_df, $grid_model, $adata, 0) -SUITE["continuous"]["collect"]["store_agent"] = - @benchmarkable collect_agent_data!($continuous_df, $continuous_model, $adata, 0) +SUITE["graph"]["collect"]["store_agent"] = @benchmarkable collect_agent_data!( + $graph_df, $graph_model, $adata, 0 +) +SUITE["grid"]["collect"]["store_agent"] = @benchmarkable collect_agent_data!( + $grid_df, $grid_model, $adata, 0 +) +SUITE["continuous"]["collect"]["store_agent"] = @benchmarkable collect_agent_data!( + $continuous_df, $continuous_model, $adata, 0 +) #### SCHEDULERS ### include("schedulers.jl") diff --git a/benchmark/ensembles.jl b/benchmark/ensembles.jl index 80ecc55469..84c0898568 100644 --- a/benchmark/ensembles.jl +++ b/benchmark/ensembles.jl @@ -15,31 +15,39 @@ function ensemble_benchmark(f, parallel, nreplicates) whensteps = 50 function genmodels(nreplicates) - basemodels = [Models.schelling(; numagents)[1] - for numagents in collect(numagents_low:numagents_high)] + basemodels = [ + Models.schelling(; numagents)[1] for + numagents in collect(numagents_low:numagents_high) + ] return repeat(basemodels, nreplicates) end if f == ensemblerun! models = genmodels(nreplicates) - adf, mdf, _ = ensemblerun!(models, schelling_agent_step!, dummystep, nsteps; - parallel, adata = [:pos, :mood, :group], - showprogress = true, - when = (model, step) -> - ( (step) % whensteps == 0 || step == 0 ), - mdata = [:min_to_be_happy]) + adf, mdf, _ = ensemblerun!( + models, + schelling_agent_step!, + dummystep, + nsteps; + parallel, + adata=[:pos, :mood, :group], + showprogress=true, + when=(model, step) -> ((step) % whensteps == 0 || step == 0), + mdata=[:min_to_be_happy], + ) else # TODO: Why do we need `replicate_idx` here? # Can't we just use the `Models.schelling`? function initialize(; - replicate_idx = 1, numagents = 320, griddims = (20, 20), min_to_be_happy = 3 + replicate_idx=1, numagents=320, griddims=(20, 20), min_to_be_happy=3 ) - space = GridSpace(griddims, periodic = false) + space = GridSpace(griddims; periodic=false) properties = Dict(:min_to_be_happy => min_to_be_happy) - model = ABM(SchellingAgent, space; - properties = properties, scheduler = Schedulers.randomly) + model = ABM( + SchellingAgent, space; properties=properties, scheduler=Schedulers.randomly + ) for n in 1:numagents agent = SchellingAgent(n, (1, 1), false, n < numagents / 2 ? 1 : 2) @@ -54,13 +62,17 @@ function ensemble_benchmark(f, parallel, nreplicates) :replicate_idx => collect(1:nreplicates), :griddims => (20, 20), ) - paramscan(parameters, initialize; - parallel, adata = [:pos, :mood, :group], - mdata = [:min_to_be_happy], showprogress = true, - agent_step! = schelling_agent_step!, - when = (model, step) -> - ( (step) % whensteps == 0 || step == 0 ), - n = nsteps) + paramscan( + parameters, + initialize; + parallel, + adata=[:pos, :mood, :group], + mdata=[:min_to_be_happy], + showprogress=true, + (agent_step!)=schelling_agent_step!, + when=(model, step) -> ((step) % whensteps == 0 || step == 0), + n=nsteps, + ) end end @@ -72,7 +84,8 @@ for (f, parallel, nreplicates, name) in [ (paramscan, false, 10, "paramscan serial 10 reps"), (paramscan, true, 10, "paramscan parallel 10 reps"), (paramscan, false, 20, "paramscan serial 20 reps"), - (paramscan, true, 20, "paramscan parallel 20 reps") + (paramscan, true, 20, "paramscan parallel 20 reps"), ] - ENSEMBLES_SUITE[name] = @benchmarkable ensemble_benchmark($f, $parallel, $nreplicates) samples = 1 + ENSEMBLES_SUITE[name] = @benchmarkable ensemble_benchmark($f, $parallel, $nreplicates) samples = + 1 end diff --git a/benchmark/schedulers.jl b/benchmark/schedulers.jl index 92168cf5f2..5e3af7ee09 100644 --- a/benchmark/schedulers.jl +++ b/benchmark/schedulers.jl @@ -19,11 +19,11 @@ function fake_model(; nagents, scheduler) for i in 1:nagents add_agent!(model, i % 2, 0) end - model + return model end function fake_model_multi(; nagents, scheduler) - model = ABM(Union{FakeAgent,OtherFakeAgent}; scheduler, warn = false) + model = ABM(Union{FakeAgent,OtherFakeAgent}; scheduler, warn=false) for i in 1:nagents if i % 2 == 0 add_agent!(FakeAgent(i, 0, 0), model) @@ -31,7 +31,7 @@ function fake_model_multi(; nagents, scheduler) add_agent!(OtherFakeAgent(i, 1, 0), model) end end - model + return model end agent_step(agent, model) = agent.incr += 1 @@ -39,17 +39,26 @@ agent_step(agent, model) = agent.incr += 1 SUITE["large_model"] = BenchmarkGroup() for (model, name) in [ - (fake_model(; nagents = 800000, scheduler = Schedulers.fastest), "fastest"), - (fake_model(; nagents = 800000, scheduler = Schedulers.by_id), "by_id"), - (fake_model(; nagents = 800000, scheduler = Schedulers.ByID()), "ByID"), - (fake_model(; nagents = 800000, scheduler = Schedulers.randomly), "randomly"), - (fake_model(; nagents = 800000, scheduler = Schedulers.Randomly()), "Randomly"), - (fake_model(; nagents = 800000, scheduler = Schedulers.by_property(:group)), "by_property"), - (fake_model(; nagents = 800000, scheduler = Schedulers.ByProperty(:group)), "ByProperty"), - (fake_model(; nagents = 800000, scheduler = Schedulers.partially(0.7)), "partially"), - (fake_model(; nagents = 800000, scheduler = Schedulers.Partially(0.7)), "Partially"), - (fake_model_multi(; nagents = 800000, scheduler = Schedulers.by_type(true, true)), "by_type"), - (fake_model_multi(; nagents = 800000, scheduler = Schedulers.ByType(true, true, Union{FakeAgent,OtherFakeAgent})), "ByType") + (fake_model(; nagents=800000, scheduler=Schedulers.fastest), "fastest"), + (fake_model(; nagents=800000, scheduler=Schedulers.by_id), "by_id"), + (fake_model(; nagents=800000, scheduler=Schedulers.ByID()), "ByID"), + (fake_model(; nagents=800000, scheduler=Schedulers.randomly), "randomly"), + (fake_model(; nagents=800000, scheduler=Schedulers.Randomly()), "Randomly"), + (fake_model(; nagents=800000, scheduler=Schedulers.by_property(:group)), "by_property"), + (fake_model(; nagents=800000, scheduler=Schedulers.ByProperty(:group)), "ByProperty"), + (fake_model(; nagents=800000, scheduler=Schedulers.partially(0.7)), "partially"), + (fake_model(; nagents=800000, scheduler=Schedulers.Partially(0.7)), "Partially"), + ( + fake_model_multi(; nagents=800000, scheduler=Schedulers.by_type(true, true)), + "by_type", + ), + ( + fake_model_multi(; + nagents=800000, + scheduler=Schedulers.ByType(true, true, Union{FakeAgent,OtherFakeAgent}), + ), + "ByType", + ), ] SUITE["large_model"][name] = @benchmarkable step!($model, agent_step, dummystep) end @@ -57,17 +66,23 @@ end SUITE["medium_model"] = BenchmarkGroup() for (model, name) in [ - (fake_model(; nagents = 2000, scheduler = Schedulers.fastest), "fastest"), - (fake_model(; nagents = 2000, scheduler = Schedulers.by_id), "by_id"), - (fake_model(; nagents = 2000, scheduler = Schedulers.ByID()), "ByID"), - (fake_model(; nagents = 2000, scheduler = Schedulers.randomly), "randomly"), - (fake_model(; nagents = 2000, scheduler = Schedulers.Randomly()), "Randomly"), - (fake_model(; nagents = 2000, scheduler = Schedulers.by_property(:group)), "by_property"), - (fake_model(; nagents = 2000, scheduler = Schedulers.ByProperty(:group)), "ByProperty"), - (fake_model(; nagents = 2000, scheduler = Schedulers.partially(0.7)), "partially"), - (fake_model(; nagents = 2000, scheduler = Schedulers.Partially(0.7)), "Partially"), - (fake_model_multi(; nagents = 2000, scheduler = Schedulers.by_type(true, true)), "by_type"), - (fake_model_multi(; nagents = 2000, scheduler = Schedulers.ByType(true, true, Union{FakeAgent,OtherFakeAgent})), "ByType") + (fake_model(; nagents=2000, scheduler=Schedulers.fastest), "fastest"), + (fake_model(; nagents=2000, scheduler=Schedulers.by_id), "by_id"), + (fake_model(; nagents=2000, scheduler=Schedulers.ByID()), "ByID"), + (fake_model(; nagents=2000, scheduler=Schedulers.randomly), "randomly"), + (fake_model(; nagents=2000, scheduler=Schedulers.Randomly()), "Randomly"), + (fake_model(; nagents=2000, scheduler=Schedulers.by_property(:group)), "by_property"), + (fake_model(; nagents=2000, scheduler=Schedulers.ByProperty(:group)), "ByProperty"), + (fake_model(; nagents=2000, scheduler=Schedulers.partially(0.7)), "partially"), + (fake_model(; nagents=2000, scheduler=Schedulers.Partially(0.7)), "Partially"), + (fake_model_multi(; nagents=2000, scheduler=Schedulers.by_type(true, true)), "by_type"), + ( + fake_model_multi(; + nagents=2000, + scheduler=Schedulers.ByType(true, true, Union{FakeAgent,OtherFakeAgent}), + ), + "ByType", + ), ] SUITE["medium_model"][name] = @benchmarkable step!($model, agent_step, dummystep) end @@ -75,19 +90,25 @@ end SUITE["small_model"] = BenchmarkGroup() for (model, name) in [ - (fake_model(; nagents = 50, scheduler = Schedulers.fastest), "fastest"), - (fake_model(; nagents = 50, scheduler = Schedulers.by_id), "by_id"), - (fake_model(; nagents = 50, scheduler = Schedulers.ByID()), "ByID"), - (fake_model(; nagents = 50, scheduler = Schedulers.randomly), "randomly"), - (fake_model(; nagents = 50, scheduler = Schedulers.Randomly()), "Randomly"), - (fake_model(; nagents = 50, scheduler = Schedulers.by_property(:group)), "by_property"), - (fake_model(; nagents = 50, scheduler = Schedulers.ByProperty(:group)), "ByProperty"), - (fake_model(; nagents = 50, scheduler = Schedulers.partially(0.7)), "partially"), - (fake_model(; nagents = 50, scheduler = Schedulers.Partially(0.7)), "Partially"), - (fake_model_multi(; nagents = 50, scheduler = Schedulers.by_type(true, true)), "by_type"), - (fake_model_multi(; nagents = 50, scheduler = Schedulers.ByType(true, true, Union{FakeAgent,OtherFakeAgent})), "ByType") + (fake_model(; nagents=50, scheduler=Schedulers.fastest), "fastest"), + (fake_model(; nagents=50, scheduler=Schedulers.by_id), "by_id"), + (fake_model(; nagents=50, scheduler=Schedulers.ByID()), "ByID"), + (fake_model(; nagents=50, scheduler=Schedulers.randomly), "randomly"), + (fake_model(; nagents=50, scheduler=Schedulers.Randomly()), "Randomly"), + (fake_model(; nagents=50, scheduler=Schedulers.by_property(:group)), "by_property"), + (fake_model(; nagents=50, scheduler=Schedulers.ByProperty(:group)), "ByProperty"), + (fake_model(; nagents=50, scheduler=Schedulers.partially(0.7)), "partially"), + (fake_model(; nagents=50, scheduler=Schedulers.Partially(0.7)), "Partially"), + (fake_model_multi(; nagents=50, scheduler=Schedulers.by_type(true, true)), "by_type"), + ( + fake_model_multi(; + nagents=50, + scheduler=Schedulers.ByType(true, true, Union{FakeAgent,OtherFakeAgent}), + ), + "ByType", + ), ] SUITE["small_model"][name] = @benchmarkable step!($model, agent_step, dummystep) end -results = run(SUITE, verbose = true, seconds = 5) +results = run(SUITE; verbose=true, seconds=5) diff --git a/benchmark/simple_grid/abusive_unkillable.jl b/benchmark/simple_grid/abusive_unkillable.jl index bdf9a597ee..67b245817e 100644 --- a/benchmark/simple_grid/abusive_unkillable.jl +++ b/benchmark/simple_grid/abusive_unkillable.jl @@ -19,7 +19,7 @@ function initialize_abusiveunkillable() model = AbusiveUnkillableModel(AbusiveUnkillableAgent[], fill(0, grid_size...)) N = floor(Int, grid_size[1] * grid_size[2] * grid_occupation * 0.5) - for i in 1:2N + for i in 1:(2N) grp = i <= N ? 1 : 2 pos = (rand(1:grid_size[1]), rand(1:grid_size[2])) while model.space[pos...] > 0 @@ -67,7 +67,11 @@ end model_abusiveunkillable = initialize_abusiveunkillable() println("Benchmarking abusive unkillable version") -@btime simulate_abusiveunkillable!($model_abusiveunkillable) setup = (model_abusiveunkillable = initialize_abusiveunkillable()) +@btime simulate_abusiveunkillable!($model_abusiveunkillable) setup = ( + model_abusiveunkillable = initialize_abusiveunkillable() +) println("Benchmarking abusive unkillable version: count nearby same") model_abusiveunkillable = initialize_abusiveunkillable() -@btime count_nearby_same_abusiveunkillable(agent, model_abusiveunkillable) setup = (agent = rand(model_abusiveunkillable.agents)) +@btime count_nearby_same_abusiveunkillable(agent, model_abusiveunkillable) setup = ( + agent = rand(model_abusiveunkillable.agents) +) diff --git a/benchmark/simple_grid/continuous_space_impact.jl b/benchmark/simple_grid/continuous_space_impact.jl index d3f84cafce..7843633bf3 100644 --- a/benchmark/simple_grid/continuous_space_impact.jl +++ b/benchmark/simple_grid/continuous_space_impact.jl @@ -7,7 +7,7 @@ spacing = 0.1 r = 0.1 space = ContinuousSpace(extent, spacing) @agent Agent ContinuousAgent{2} begin end -model = ABM(Agent, space; rng = StableRNG(42)) +model = ABM(Agent, space; rng=StableRNG(42)) # fill with random agents N = 1000 @@ -47,7 +47,6 @@ Continuous space count nearby ids, spacing=0.05, r=0.1, exact Continuous space count nearby ids, spacing=0.1, r=0.1, exact 4.843 μs (58 allocations: 5.22 KiB) - # master Continuous space count nearby ids, spacing=0.05, r=0.1, inexact 3.612 μs (71 allocations: 4.91 KiB) @@ -58,4 +57,4 @@ Continuous space count nearby ids, spacing=0.1, r=0.1, inexact 5.900 μs (118 allocations: 8.05 KiB) Continuous space count nearby ids, spacing=0.1, r=0.1, exact 10.300 μs (218 allocations: 32.88 KiB) - =# \ No newline at end of file + =# diff --git a/benchmark/simple_grid/dict_based.jl b/benchmark/simple_grid/dict_based.jl index 2d73d3cd6f..83e419c8f0 100644 --- a/benchmark/simple_grid/dict_based.jl +++ b/benchmark/simple_grid/dict_based.jl @@ -4,8 +4,8 @@ mutable struct DictAgent end function initialize_dict() - model = Dict{NTuple{2, Int}, DictAgent}() - N = grid_size[1]*grid_size[2]*grid_occupation + model = Dict{NTuple{2,Int},DictAgent}() + N = grid_size[1] * grid_size[2] * grid_occupation for n in 1:N group = n < N / 2 ? 1 : 2 pos = (rand(1:grid_size[1]), rand(1:grid_size[2])) @@ -49,4 +49,4 @@ end model_dict = initialize_dict() println("benchmarking dict-based step") -@btime simulation_step!($model_dict) setup = (model_dict = initialize_dict()) \ No newline at end of file +@btime simulation_step!($model_dict) setup = (model_dict = initialize_dict()) diff --git a/benchmark/simple_grid/gridspace_based.jl b/benchmark/simple_grid/gridspace_based.jl index c0246bacf0..a1d0c519b1 100644 --- a/benchmark/simple_grid/gridspace_based.jl +++ b/benchmark/simple_grid/gridspace_based.jl @@ -1,16 +1,16 @@ mutable struct GridSpaceAgent <: AbstractAgent id::Int - pos::NTuple{2, Int} # Notice that position type depends on space-to-be-used + pos::NTuple{2,Int} # Notice that position type depends on space-to-be-used group::Int happy::Bool end function initialize_gridspace() - space = GridSpace(grid_size; periodic = false) + space = GridSpace(grid_size; periodic=false) properties = Dict(:min_to_be_happy => min_to_be_happy) rng = Random.Xoshiro(rand(UInt)) model = ABM(GridSpaceAgent, space; properties, rng) - N = grid_size[1]*grid_size[2]*grid_occupation + N = grid_size[1] * grid_size[2] * grid_occupation for n in 1:N group = n < N / 2 ? 1 : 2 agent = GridSpaceAgent(n, (1, 1), group, false) @@ -26,7 +26,7 @@ function agent_step_gridspace!(agent, model) else move_agent_single!(agent, model) end - return + return nothing end function count_nearby_same(agent, model) nearby_same = 0 @@ -47,12 +47,12 @@ println("Benchmarking GridSpace version: count nearby same") model = initialize_gridspace() @btime count_nearby_same(agent, model) setup = (agent = random_agent(model)) -function profile_nearby_same(model, agent = random_agent(model)) +function profile_nearby_same(model, agent=random_agent(model)) x = 0 N = 1000000 for i in 1:N x += count_nearby_same(agent, model) end - return x/N + return x / N end -# Then use @profview in VSCode \ No newline at end of file +# Then use @profview in VSCode diff --git a/benchmark/simple_grid/run_comparison.jl b/benchmark/simple_grid/run_comparison.jl index 55c30f6c32..0bcf74a1bf 100644 --- a/benchmark/simple_grid/run_comparison.jl +++ b/benchmark/simple_grid/run_comparison.jl @@ -4,13 +4,9 @@ Random.seed!(1234) const min_to_be_happy = 3 # how many nearby agents you need of same group const grid_occupation = 0.8 # percentage of space occupied by agents const grid_size = (30, 30) -const moore = [ - (1,0), (1,1), (1,-1), - (0,1), (0,-1), - (-1,0), (-1,1), (-1,-1), -] +const moore = [(1, 0), (1, 1), (1, -1), (0, 1), (0, -1), (-1, 0), (-1, 1), (-1, -1)] include("dict_based.jl") include("gridspace_based.jl") include("simple_grid_space.jl") -include("abusive_unkillable.jl") \ No newline at end of file +include("abusive_unkillable.jl") diff --git a/benchmark/simple_grid/simple_grid_space.jl b/benchmark/simple_grid/simple_grid_space.jl index 05b2a2120d..943a10a151 100644 --- a/benchmark/simple_grid/simple_grid_space.jl +++ b/benchmark/simple_grid/simple_grid_space.jl @@ -1,17 +1,17 @@ mutable struct SoloGridSpaceAgent <: AbstractAgent id::Int - pos::NTuple{2, Int} # Notice that position type depends on space-to-be-used + pos::NTuple{2,Int} # Notice that position type depends on space-to-be-used group::Int happy::Bool end # Notice that these functions are fully identical with the GridSpace version. function initialize_sologridspace() - space = GridSpaceSingle(grid_size; periodic = false) + space = GridSpaceSingle(grid_size; periodic=false) properties = Dict(:min_to_be_happy => min_to_be_happy) rng = Random.Xoshiro(rand(UInt)) model = ABM(SoloGridSpaceAgent, space; properties, rng) - N = grid_size[1]*grid_size[2]*grid_occupation + N = grid_size[1] * grid_size[2] * grid_occupation for n in 1:N group = n < N / 2 ? 1 : 2 agent = SoloGridSpaceAgent(n, (1, 1), group, false) @@ -27,7 +27,7 @@ function agent_step_sologridspace!(agent, model) else move_agent_single!(agent, model) end - return + return nothing end function count_nearby_same(agent, model) nearby_same = 0 @@ -41,7 +41,9 @@ end model_sologridspace = initialize_sologridspace() println("Benchmarking GridSpaceSingle version") -@btime step!($model_sologridspace, agent_step_sologridspace!) setup = (model_sologridspace = initialize_sologridspace()) +@btime step!($model_sologridspace, agent_step_sologridspace!) setup = ( + model_sologridspace = initialize_sologridspace() +) println("Benchmarking GridSpaceSingle version: count nearby same") model = initialize_sologridspace() diff --git a/docs/logo/logo.jl b/docs/logo/logo.jl index 75fd96c404..457b947651 100644 --- a/docs/logo/logo.jl +++ b/docs/logo/logo.jl @@ -7,7 +7,7 @@ using InteractiveDynamics # Some nice colors: pink_green = Dict(:S => "#2b2b33", :I => "#a533d6", :R => "#338c54") -pink_green_dark = Dict(:S => "#d068fc", :I => "#72cc94", :R => "#f0f0f0") +pink_green_dark = Dict(:S => "#d068fc", :I => "#72cc94", :R => "#f0f0f0") juliadynamics = Dict( :S => JULIADYNAMICS_COLORS[3], :I => JULIADYNAMICS_COLORS[1], @@ -30,11 +30,10 @@ Makie.renderstring!( font_matrix, "Agents.jl", font, - round(Int, logo_dims[2]/2), - round(Int, y/2 + logo_dims[2]/6) , - round(Int, x/2), - halign = :hcenter, - + round(Int, logo_dims[2] / 2), + round(Int, y / 2 + logo_dims[2] / 6), + round(Int, x / 2); + halign=:hcenter, ) # Use this to test how the font looks like: @@ -47,10 +46,10 @@ include("logo_model_def.jl") static_preplot!(ax, model) = hidedecorations!(ax) ax_kwargs = (; - leftspinecolor = BLACK, - rightspinecolor = BLACK, - topspinecolor = BLACK, - bottomspinecolor = BLACK, + leftspinecolor=BLACK, + rightspinecolor=BLACK, + topspinecolor=BLACK, + bottomspinecolor=BLACK, backgroundcolor, # ax.leftspinevisible = false, # ax.rightspinevisible = false, @@ -59,33 +58,50 @@ ax_kwargs = (; ) # Test: -sir = sir_logo_initiation(; N = 400, interaction_radius = 0.035) -fig, ax = abmplot(sir; +sir = sir_logo_initiation(; N=400, interaction_radius=0.035) +fig, ax = abmplot( + sir; # agent_step! = sir_agent_step!, model_step! = sir_model_step!, - enable_inspection = false, - ac = sir_colors, as = 9, static_preplot!, - figure = (resolution = logo_dims, ), axis = ax_kwargs, + enable_inspection=false, + ac=sir_colors, + as=9, + static_preplot!, + figure=(resolution=logo_dims,), + axis=ax_kwargs, ) display(fig) # %% actually make the video using CairoMakie CairoMakie.activate!() -sir = sir_logo_initiation(; N = 600, interaction_radius = 0.035) -abmvideo("agents4_logo.gif", sir, sir_agent_step!, sir_model_step!; - ac = sir_colors, as = 9, static_preplot!, - figure = (resolution = logo_dims, backgroundcolor), - axis = ax_kwargs, showstep = false, +sir = sir_logo_initiation(; N=600, interaction_radius=0.035) +abmvideo( + "agents4_logo.gif", + sir, + sir_agent_step!, + sir_model_step!; + ac=sir_colors, + as=9, + static_preplot!, + figure=(resolution=logo_dims, backgroundcolor), + axis=ax_kwargs, + showstep=false, # For gif: - spf = 4, framerate = 30, frames = 450, + spf=4, + framerate=30, + frames=450, # For mp4: # spf = 2, framerate = 60, frames = 900, ) # %% Save final figure for making the icon -fig, ax = abmplot(sir; - enable_inspection = false, - ac = sir_colors, as = 9, static_preplot!, - figure = (resolution = logo_dims, backgroundcolor), axis = ax_kwargs, +fig, ax = abmplot( + sir; + enable_inspection=false, + ac=sir_colors, + as=9, + static_preplot!, + figure=(resolution=logo_dims, backgroundcolor), + axis=ax_kwargs, ) CairoMakie.save("agents_for_icon.png", fig) diff --git a/docs/logo/logo_model_def.jl b/docs/logo/logo_model_def.jl index b6a4625dd2..4d081927db 100644 --- a/docs/logo/logo_model_def.jl +++ b/docs/logo/logo_model_def.jl @@ -23,15 +23,15 @@ end function transmit!(a1, a2, rp, rng) ## for transmission, only 1 can have the disease (otherwise nothing happens) - count(a.status == :I for a in (a1, a2)) ≠ 1 && return + count(a.status == :I for a in (a1, a2)) ≠ 1 && return nothing infected, healthy = a1.status == :I ? (a1, a2) : (a2, a1) - rand(rng) > infected.β && return + rand(rng) > infected.β && return nothing if healthy.status == :R - rand(rng) > rp && return + rand(rng) > rp && return nothing end - healthy.status = :I + return healthy.status = :I end function sir_model_step!(model) @@ -45,7 +45,7 @@ end function sir_agent_step!(agent, model) move_agent!(agent, model, model.dt) update!(agent) - recover_or_die!(agent, model) + return recover_or_die!(agent, model) end update!(agent) = agent.status == :I && (agent.days_infected += 1) @@ -65,33 +65,25 @@ function recover_or_die!(agent, model) end function sir_logo_initiation(; - recovery_period = RECOVERY_PERIOD * steps_per_day, - reinfection_probability = REINFECT, - isolated = 0.0, # in percentage - interaction_radius = 0.014, - dt = 1.0, - speed = SPEED, - death_rate = 0.044, # from website of WHO - N = 1000, # Agents outside text - initial_infected = INITINFECT, - seed = SEED, - β = BETA, - ) + recovery_period=RECOVERY_PERIOD * steps_per_day, + reinfection_probability=REINFECT, + isolated=0.0, # in percentage + interaction_radius=0.014, + dt=1.0, + speed=SPEED, + death_rate=0.044, # from website of WHO + N=1000, # Agents outside text + initial_infected=INITINFECT, + seed=SEED, + β=BETA, +) # Sample agents inside text points = sample(findall(p -> p > 0.0, font_matrix), AGENTS_IN_TEXT) # Convert those into points that make sense within our continuous space (image coords must be y-inverted) - init_static = map( - p -> (p.I[2]/100, (logo_dims[2] - p.I[1])/100), - points, - ) + init_static = map(p -> (p.I[2] / 100, (logo_dims[2] - p.I[1]) / 100), points) - properties = (; - reinfection_probability, - death_rate, - interaction_radius, - dt, - ) + properties = (; reinfection_probability, death_rate, interaction_radius, dt) rng = Random.Xoshiro(seed) space = ContinuousSpace(logo_dims ./ 100) model = ABM(PoorSoul, space; rng, properties) @@ -103,17 +95,17 @@ function sir_logo_initiation(; status = :S mass = Inf vel = (0.0, 0.0) - p = round(Int, recovery_period*(rand(rng)*0.4 + 0.8)) + p = round(Int, recovery_period * (rand(rng) * 0.4 + 0.8)) add_agent!(pos, model, vel, mass, 0, status, β, p) end #-------------------------------------- ## Add initial individuals for ind in 1:N - status = ind ≤ N - initial_infected*N ? :S : :I + status = ind ≤ N - initial_infected * N ? :S : :I isisolated = ind ≤ isolated * N mass = isisolated ? Inf : 1.0 vel = isisolated ? (0.0, 0.0) : sincos(2π * rand(rng)) .* speed - p = round(Int, recovery_period*(rand(rng)*0.4 + 0.8)) + p = round(Int, recovery_period * (rand(rng) * 0.4 + 0.8)) add_agent!(model, vel, mass, 0, status, β, p) end return model diff --git a/docs/make.jl b/docs/make.jl index 63c5433e0a..029f39a842 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -8,7 +8,7 @@ using Documenter println("Agents...") using Agents println("Literate...") -import Literate +using Literate: Literate println("InteractiveDynamics...") using InteractiveDynamics println("LightOSM...") @@ -18,25 +18,25 @@ println("Converting Examples...") indir = joinpath(@__DIR__, "..", "examples") outdir = joinpath(@__DIR__, "src", "examples") -rm(outdir; force = true, recursive = true) # cleans up previous examples +rm(outdir; force=true, recursive=true) # cleans up previous examples mkpath(outdir) toskip = () for file in readdir(indir) file ∈ toskip && continue - Literate.markdown(joinpath(indir, file), outdir; credit = false) + Literate.markdown(joinpath(indir, file), outdir; credit=false) end # Also bring in visualizations from interactive dynamics docs: using Literate infile = joinpath(pkgdir(InteractiveDynamics), "docs", "src", "agents.jl") outdir = joinpath(@__DIR__, "src") -Literate.markdown(infile, outdir; credit = false, name = "agents_visualizations") +Literate.markdown(infile, outdir; credit=false, name="agents_visualizations") # %% # download the themes println("Theme-ing") -using DocumenterTools:Themes -import Downloads +using DocumenterTools: Themes +using Downloads: Downloads for file in ( "juliadynamics-lightdefs.scss", "juliadynamics-darkdefs.scss", @@ -66,22 +66,22 @@ Themes.compile( # %% println("Documentation Build") ENV["JULIA_DEBUG"] = "Documenter" -makedocs( - modules = [Agents, InteractiveDynamics, LightOSM], - sitename = "Agents.jl", - authors = "Tim DuBois, George Datseris, Aayush Sabharwal, Ali R. Vahdati and contributors.", - doctest = false, - format = Documenter.HTML( - prettyurls = CI, - assets = [ +makedocs(; + modules=[Agents, InteractiveDynamics, LightOSM], + sitename="Agents.jl", + authors="Tim DuBois, George Datseris, Aayush Sabharwal, Ali R. Vahdati and contributors.", + doctest=false, + format=Documenter.HTML(; + prettyurls=CI, + assets=[ asset( - "https://fonts.googleapis.com/css?family=Montserrat|Source+Code+Pro&display=swap", - class = :css, + "https://fonts.googleapis.com/css?family=Montserrat|Source+Code+Pro&display=swap"; + class=:css, ), ], - collapselevel = 1, + collapselevel=1, ), - pages = [ + pages=[ "Introduction" => "index.md", "Tutorial" => "tutorial.md", "Examples" => [ @@ -92,7 +92,7 @@ makedocs( "examples/predator_prey.md", "examples/rabbit_fox_hawk.md", "models.md", - "examples.md" + "examples.md", ], "api.md", "Plotting and Interactivity" => "agents_visualizations.md", @@ -111,11 +111,11 @@ makedocs( @info "Deploying Documentation" if CI - deploydocs( - repo = "github.com/JuliaDynamics/Agents.jl.git", - target = "build", - push_preview = true, - devbranch = "main", + deploydocs(; + repo="github.com/JuliaDynamics/Agents.jl.git", + target="build", + push_preview=true, + devbranch="main", ) end diff --git a/docs/src/distances_example_plot.jl b/docs/src/distances_example_plot.jl index 99324e5349..a65b4b1fc1 100644 --- a/docs/src/distances_example_plot.jl +++ b/docs/src/distances_example_plot.jl @@ -1,9 +1,9 @@ using CairoMakie -fig = Figure(resolution = (850, 300)) +fig = Figure(; resolution=(850, 300)) function circle!(ax, r, color, distance) if distance == :euclidean - θ = 0:0.01:2π + θ = 0:0.01:(2π) lines!(ax, r .* cos.(θ), r .* sin.(θ); color) elseif distance == :chebyshev lines!(ax, [-r, -r], [-r, r]; color) @@ -20,27 +20,27 @@ end function scatter_dots!(ax, r) r0 = ceil(r) - X = -r0:r0 + X = (-r0):r0 points = [Point2f(x, y) for x in X for y in X] - scatter!(ax, points; color = :black) + return scatter!(ax, points; color=:black) end rs = [1, 2, 3.4] colors = [:blue, :red, :orange] for (i, distance) in enumerate((:euclidean, :chebyshev, :manhattan)) - ax = Axis(fig[1, i]; title = string(distance)) + ax = Axis(fig[1, i]; title=string(distance)) scatter_dots!(ax, maximum(rs)) for (j, r) in enumerate(rs) label = j == i ? "r = $(r)" : "" circle!(ax, r, colors[j], distance) end ax.xticks = ax.yticks = -4:2:4 - i > 1 && hideydecorations!(ax; grid = false) + i > 1 && hideydecorations!(ax; grid=false) end -elems = [LineElement(color = c, linestyle = nothing) for c in colors] +elems = [LineElement(; color=c, linestyle=nothing) for c in colors] -fig[1, 4] = Legend(fig[1,4], elems, string.(rs), "radius", framevisible = false) +fig[1, 4] = Legend(fig[1, 4], elems, string.(rs), "radius"; framevisible=false) -fig \ No newline at end of file +fig diff --git a/examples/celllistmap.jl b/examples/celllistmap.jl index 55199b25fe..344a9591e6 100644 --- a/examples/celllistmap.jl +++ b/examples/celllistmap.jl @@ -81,7 +81,7 @@ function initialize_model(; sides=SVector(500.0, 500.0), dt=0.001, max_radius=10.0, - parallel=true + parallel=true, ) ## initial random positions positions = [sides .* rand(SVector{2,Float64}) for _ in 1:number_of_particles] @@ -93,7 +93,7 @@ function initialize_model(; space2d = ContinuousSpace(Tuple(sides); periodic=true) ## Initialize CellListMap periodic system - system = PeriodicSystem( + system = PeriodicSystem(; positions=positions, unitcell=sides, cutoff=2 * max_radius, @@ -104,20 +104,13 @@ function initialize_model(; ## define the model properties ## The clmap_system field contains the data required for CellListMap.jl - properties = ( - dt=dt, - number_of_particles=number_of_particles, - system=system, - ) - model = ABM(Particle, - space2d, - properties=properties - ) + properties = (dt=dt, number_of_particles=number_of_particles, system=system) + model = ABM(Particle, space2d; properties=properties) ## Create active agents for id in 1:number_of_particles add_agent_pos!( - Particle( + Particle(; id=id, r=(0.5 + 0.9 * rand()) * max_radius, k=(10 + 20 * rand()), # random force constants @@ -125,7 +118,8 @@ function initialize_model(; pos=Tuple(positions[id]), vel=(100 * randn(), 100 * randn()), # initial velocities ), - model) + model, + ) end return model @@ -200,11 +194,9 @@ end # Finally, the function below runs an example simulation, for 1000 steps. function simulate(model=nothing; nsteps=1_000, number_of_particles=10_000) if isnothing(model) - model = initialize_model(number_of_particles=number_of_particles) + model = initialize_model(; number_of_particles=number_of_particles) end - Agents.step!( - model, agent_step!, model_step!, nsteps, false, - ) + return Agents.step!(model, agent_step!, model_step!, nsteps, false) end # Which should be quite fast model = initialize_model() @@ -218,13 +210,18 @@ simulate(model) # compile using InteractiveDynamics using CairoMakie CairoMakie.activate!() # hide -model = initialize_model(number_of_particles=1000) +model = initialize_model(; number_of_particles=1000) abmvideo( - "celllistmap.mp4", model, agent_step!, model_step!; - framerate=20, frames=200, spf=5, + "celllistmap.mp4", + model, + agent_step!, + model_step!; + framerate=20, + frames=200, + spf=5, title="Bouncing particles with CellListMap.jl acceleration", as=p -> p.r, # marker size - ac=p -> p.k # marker color + ac=p -> p.k, # marker color ) # ```@raw html diff --git a/examples/diffeq.jl b/examples/diffeq.jl index c696330a41..6a18699e2f 100644 --- a/examples/diffeq.jl +++ b/examples/diffeq.jl @@ -43,19 +43,22 @@ end function agent_step!(agent, model) ## Make sure we sample from the fish distribution - agent.yearly_catch = rand(model.rng, Poisson(agent.competence)) + return agent.yearly_catch = rand(model.rng, Poisson(agent.competence)) end function dstock(model) ## Only allow fishing if stocks are high enough - h = model.stock > model.min_threshold ? sum(a.yearly_catch for a in allagents(model)) : + h = if model.stock > model.min_threshold + sum(a.yearly_catch for a in allagents(model)) + else 0.0 + end - model.stock * (1 - (model.stock / model.max_population)) - h + return model.stock * (1 - (model.stock / model.max_population)) - h end function model_step!(model) - model.stock += dstock(model) + return model.stock += dstock(model) end nothing #hide @@ -71,14 +74,14 @@ nothing #hide # with some competence. function initialise(; - stock = 5.0, # Initial population of fish - max_population = 500.0, # Maximum value of fish stock - min_threshold = 60.0, # Regulate fishing if population drops below this value - nagents = 50, + stock=5.0, # Initial population of fish + max_population=500.0, # Maximum value of fish stock + min_threshold=60.0, # Regulate fishing if population drops below this value + nagents=50, ) model = ABM( Fisher; - properties = Dict( + properties=Dict( :stock => stock, :max_population => max_population, :min_threshold => min_threshold, @@ -93,7 +96,7 @@ function initialise(; 0.0, ) end - model + return model end nothing #hide @@ -102,17 +105,11 @@ nothing #hide Random.seed!(6549) #hide model = initialise() -_, results = run!(model, agent_step!, model_step!, 20; mdata = [:stock]) - -f = Figure(resolution = (600, 400)) -ax = - f[1, 1] = Axis( - f, - xlabel = "Year", - ylabel = "Stock", - title = "Fishery Inventory", - ) -lines!(ax, results.stock, linewidth = 2, color = :blue) +_, results = run!(model, agent_step!, model_step!, 20; mdata=[:stock]) + +f = Figure(; resolution=(600, 400)) +ax = f[1, 1] = Axis(f; xlabel="Year", ylabel="Stock", title="Fishery Inventory") +lines!(ax, results.stock; linewidth=2, color=:blue) f # ### Add in some bureaucracy @@ -136,26 +133,29 @@ end function dstock(model) ## Only allow fishing if stocks are high enough ## (monitored yearly, so this will return 0 364 days of the year) - h = model.tick % 365 == 0 && model.stock > model.min_threshold ? - sum(a.yearly_catch for a in allagents(model)) : 0.0 + h = if model.tick % 365 == 0 && model.stock > model.min_threshold + sum(a.yearly_catch for a in allagents(model)) + else + 0.0 + end - model.stock * (1 - (model.stock / model.max_population)) - h + return model.stock * (1 - (model.stock / model.max_population)) - h end function model_step!(model) model.tick += 1 - model.stock += dstock(model) + return model.stock += dstock(model) end function initialise(; - stock = 400.0, # Initial population of fish (lets move to an equilibrium position) - max_population = 500.0, # Maximum value of fish stock - min_threshold = 60.0, # Regulate fishing if population drops below this value - nagents = 50, + stock=400.0, # Initial population of fish (lets move to an equilibrium position) + max_population=500.0, # Maximum value of fish stock + min_threshold=60.0, # Regulate fishing if population drops below this value + nagents=50, ) model = ABM( Fisher; - properties = Dict( + properties=Dict( :stock => stock, :max_population => max_population, :min_threshold => min_threshold, @@ -165,7 +165,7 @@ function initialise(; for _ in 1:nagents add_agent!(model, floor(rand(model.rng, truncated(LogNormal(), 1, 6))), 0.0) end - model + return model end nothing #hide @@ -175,18 +175,11 @@ nothing #hide Random.seed!(6549) #hide model = initialise() yearly(model, s) = s % 365 == 0 -_, results = - run!(model, agent_step!, model_step!, 20 * 365; mdata = [:stock], when = yearly) - -f = Figure(resolution = (600, 400)) -ax = - f[1, 1] = Axis( - f, - xlabel = "Year", - ylabel = "Stock", - title = "Fishery Inventory", - ) -lines!(ax, results.stock, linewidth = 2, color = :blue) +_, results = run!(model, agent_step!, model_step!, 20 * 365; mdata=[:stock], when=yearly) + +f = Figure(; resolution=(600, 400)) +ax = f[1, 1] = Axis(f; xlabel="Year", ylabel="Stock", title="Fishery Inventory") +lines!(ax, results.stock; linewidth=2, color=:blue) f # ### Baseline benchmark @@ -196,8 +189,9 @@ f using BenchmarkTools Random.seed!(6549) #hide -@btime Agents.step!(model, agent_step!, model_step!, 20 * 365) setup = - (model = initialise()) +@btime Agents.step!(model, agent_step!, model_step!, 20 * 365) setup = ( + model = initialise() +) # So this is fairly quick since the model is a simple one, but it's certainly not as efficient # as it could be. @@ -214,19 +208,21 @@ Random.seed!(6549) #hide # Lets therefore modify our system to solve the logistic equation in a continuous context, but # discretely monitor and harvest. -import OrdinaryDiffEq +using OrdinaryDiffEq: OrdinaryDiffEq function agent_diffeq_step!(agent, model) - agent.yearly_catch = rand(model.rng, Poisson(agent.competence)) + return agent.yearly_catch = rand(model.rng, Poisson(agent.competence)) end function model_diffeq_step!(model) ## We step 364 days with this call. OrdinaryDiffEq.step!(model.i, 364.0, true) ## Only allow fishing if stocks are high enough - model.i.p[2] = - model.i.u[1] > model.min_threshold ? sum(a.yearly_catch for a in allagents(model)) : + model.i.p[2] = if model.i.u[1] > model.min_threshold + sum(a.yearly_catch for a in allagents(model)) + else 0.0 + end ## Notify the integrator that conditions may be altered OrdinaryDiffEq.u_modified!(model.i, true) ## Then apply our catch modifier @@ -235,27 +231,27 @@ function model_diffeq_step!(model) model.stock = model.i.u[1] ## And reset for the next year model.i.p[2] = 0.0 - OrdinaryDiffEq.u_modified!(model.i, true) + return OrdinaryDiffEq.u_modified!(model.i, true) end function initialise_diffeq(; - stock = 400.0, # Initial population of fish (lets move to an equilibrium position) - max_population = 500.0, # Maximum value of fish stock - min_threshold = 60.0, # Regulate fishing if population drops below this value - nagents = 50, + stock=400.0, # Initial population of fish (lets move to an equilibrium position) + max_population=500.0, # Maximum value of fish stock + min_threshold=60.0, # Regulate fishing if population drops below this value + nagents=50, ) - function fish_stock!(ds, s, p, t) max_population, h = p - ds[1] = s[1] * (1 - (s[1] / max_population)) - h + return ds[1] = s[1] * (1 - (s[1] / max_population)) - h end - prob = - OrdinaryDiffEq.ODEProblem(fish_stock!, [stock], (0.0, Inf), [max_population, 0.0]) - integrator = OrdinaryDiffEq.init(prob, OrdinaryDiffEq.Tsit5(); advance_to_tstop = true) + prob = OrdinaryDiffEq.ODEProblem( + fish_stock!, [stock], (0.0, Inf), [max_population, 0.0] + ) + integrator = OrdinaryDiffEq.init(prob, OrdinaryDiffEq.Tsit5(); advance_to_tstop=true) model = ABM( Fisher; - properties = Dict( + properties=Dict( :stock => stock, :max_population => max_population, :min_threshold => min_threshold, @@ -265,7 +261,7 @@ function initialise_diffeq(; for _ in 1:nagents add_agent!(model, floor(rand(model.rng, truncated(LogNormal(), 1, 6))), 0.0) end - model + return model end nothing #hide @@ -289,24 +285,19 @@ nothing #hide Random.seed!(6549) #hide modeldeq = initialise_diffeq() -_, resultsdeq = run!(modeldeq, agent_diffeq_step!, model_diffeq_step!, 20; mdata = [:stock]) - -f = Figure(resolution = (600, 400)) -ax = - f[1, 1] = Axis( - f, - xlabel = "Year", - ylabel = "Stock", - title = "Fishery Inventory", - ) -lines!(ax, resultsdeq.stock, linewidth = 2, color = :blue) +_, resultsdeq = run!(modeldeq, agent_diffeq_step!, model_diffeq_step!, 20; mdata=[:stock]) + +f = Figure(; resolution=(600, 400)) +ax = f[1, 1] = Axis(f; xlabel="Year", ylabel="Stock", title="Fishery Inventory") +lines!(ax, resultsdeq.stock; linewidth=2, color=:blue) f # The small complexity addition yields us a generous speed up of around 4.5x. Random.seed!(6549) #hide -@btime Agents.step!(model, agent_diffeq_step!, model_diffeq_step!, 20) setup = - (model = initialise_diffeq()) +@btime Agents.step!(model, agent_diffeq_step!, model_diffeq_step!, 20) setup = ( + model = initialise_diffeq() +) # Digging into the results a little more, we can see that the `DifferentialEquations` # solver did not need to solve the logistic equation at every agent step to achieve @@ -326,17 +317,11 @@ length(modeldeq.i.sol.t) # Compare our two results directly, both start with the same random seed and evolve in # precisely the same manner: -f = Figure(resolution = (600, 400)) -ax = - f[1, 1] = Axis( - f, - xlabel = "Year", - ylabel = "Stock", - title = "Fishery Inventory", - ) -lineE = lines!(ax, results.stock, linewidth = 2, color = :blue) -lineTS = lines!(ax, resultsdeq.stock, linewidth = 2, color = :red) -leg = f[1, end+1] = Legend(f, [lineE, lineTS], ["Euler", "TSit5"]) +f = Figure(; resolution=(600, 400)) +ax = f[1, 1] = Axis(f; xlabel="Year", ylabel="Stock", title="Fishery Inventory") +lineE = lines!(ax, results.stock; linewidth=2, color=:blue) +lineTS = lines!(ax, resultsdeq.stock; linewidth=2, color=:red) +leg = f[1, end + 1] = Legend(f, [lineE, lineTS], ["Euler", "TSit5"]) f # That's an average discrepancy of 30 fish! Optimising the step size in the Euler method @@ -357,16 +342,16 @@ f # to handle the agent based aspects of our problem. function agent_cb_step!(agent, model) - agent.yearly_catch = rand(model.rng, Poisson(agent.competence)) + return agent.yearly_catch = rand(model.rng, Poisson(agent.competence)) end -function initialise_cb(; min_threshold = 60.0, nagents = 50) - model = ABM(Fisher; properties = Dict(:min_threshold => min_threshold)) +function initialise_cb(; min_threshold=60.0, nagents=50) + model = ABM(Fisher; properties=Dict(:min_threshold => min_threshold)) for _ in 1:nagents add_agent!(model, floor(rand(model.rng, truncated(LogNormal(), 1, 6))), 0.0) end - model + return model end Random.seed!(759) #hide @@ -374,17 +359,20 @@ modelcb = initialise_cb() # That's it for the `Agents` side of things! Now to build the ODE. -import DiffEqCallbacks +using DiffEqCallbacks: DiffEqCallbacks function fish!(integrator, model) - integrator.p[2] = integrator.u[1] > model.min_threshold ? - sum(a.yearly_catch for a in allagents(model)) : 0.0 - Agents.step!(model, agent_cb_step!, 1) + integrator.p[2] = if integrator.u[1] > model.min_threshold + sum(a.yearly_catch for a in allagents(model)) + else + 0.0 + end + return Agents.step!(model, agent_cb_step!, 1) end function fish_stock!(ds, s, p, t) max_population, h = p - ds[1] = s[1] * (1 - (s[1] / max_population)) - h + return ds[1] = s[1] * (1 - (s[1] / max_population)) - h end tspan = (0.0, 20.0 * 365.0) @@ -399,20 +387,12 @@ fish = DiffEqCallbacks.PeriodicCallback(i -> fish!(i, modelcb), 364) reset = DiffEqCallbacks.PeriodicCallback(i -> i.p[2] = 0.0, 365) sol = OrdinaryDiffEq.solve( - prob, - OrdinaryDiffEq.Tsit5(); - callback = OrdinaryDiffEq.CallbackSet(fish, reset), + prob, OrdinaryDiffEq.Tsit5(); callback=OrdinaryDiffEq.CallbackSet(fish, reset) ) -discrete = vcat(sol(0:365:(365 * 20))[:,:]...) -f = Figure(resolution = (600, 400)) -ax = - f[1, 1] = Axis( - f, - xlabel = "Year", - ylabel = "Stock", - title = "Fishery Inventory", - ) -lines!(ax, discrete, linewidth = 2, color = :blue) +discrete = vcat(sol(0:365:(365 * 20))[:, :]...) +f = Figure(; resolution=(600, 400)) +ax = f[1, 1] = Axis(f; xlabel="Year", ylabel="Stock", title="Fishery Inventory") +lines!(ax, discrete; linewidth=2, color=:blue) f # The results are different here, since the construction of this version and the one @@ -420,4 +400,3 @@ f # # However, as you can see, it is for the most part just a re-arranged implementation # of the integrator method - giving users flexibility in their architecture choices. - diff --git a/examples/flock.jl b/examples/flock.jl index 3d75b3cd98..e3be0ef999 100644 --- a/examples/flock.jl +++ b/examples/flock.jl @@ -46,20 +46,20 @@ end # The function `initialize_model` generates birds and returns # a model object using default values. function initialize_model(; - n_birds = 100, - speed = 1.0, - cohere_factor = 0.25, - separation = 4.0, - separate_factor = 0.25, - match_factor = 0.01, - visual_distance = 5.0, - extent = (100, 100), - seed = 42, + n_birds=100, + speed=1.0, + cohere_factor=0.25, + separation=4.0, + separate_factor=0.25, + match_factor=0.01, + visual_distance=5.0, + extent=(100, 100), + seed=42, ) - space2d = ContinuousSpace(extent; spacing = visual_distance/1.5) + space2d = ContinuousSpace(extent; spacing=visual_distance / 1.5) rng = Random.MersenneTwister(seed) - model = ABM(Bird, space2d; rng, scheduler = Schedulers.Randomly()) + model = ABM(Bird, space2d; rng, scheduler=Schedulers.Randomly()) for _ in 1:n_birds vel = Tuple(rand(model.rng, 2) * 2 .- 1) add_agent!( @@ -110,7 +110,7 @@ function agent_step!(bird, model) bird.vel = (bird.vel .+ cohere .+ separate .+ match) ./ 2 bird.vel = bird.vel ./ norm(bird.vel) ## Move bird according to new velocity and speed - move_agent!(bird, model, bird.speed) + return move_agent!(bird, model, bird.speed) end # ## Plotting the flock @@ -126,7 +126,7 @@ CairoMakie.activate!() # hide const bird_polygon = Polygon(Point2f[(-0.5, -0.5), (1, 0), (-0.5, 0.5)]) function bird_marker(b::Bird) φ = atan(b.vel[2], b.vel[1]) #+ π/2 + π - scale(rotate2D(bird_polygon, φ), 2) + return scale(rotate2D(bird_polygon, φ), 2) end # Where we have used the utility functions `scale` and `rotate2D` to act on a @@ -134,15 +134,18 @@ end # the `as` keyword is meaningless when using polygons as markers. model = initialize_model() -figure, = abmplot(model; am = bird_marker) +figure, = abmplot(model; am=bird_marker) figure # And let's also do a nice little video for it: abmvideo( - "flocking.mp4", model, agent_step!; - am = bird_marker, - framerate = 20, frames = 100, - title = "Flocking" + "flocking.mp4", + model, + agent_step!; + am=bird_marker, + framerate=20, + frames=100, + title="Flocking", ) # ```@raw html diff --git a/examples/measurements.jl b/examples/measurements.jl index 42cc09b6c0..488778a1d9 100644 --- a/examples/measurements.jl +++ b/examples/measurements.jl @@ -37,7 +37,7 @@ end using CairoMakie using Statistics: mean import DrWatson: @dict -import StatsBase +using StatsBase: StatsBase CairoMakie.activate!() # hide using Random # hide @@ -52,7 +52,7 @@ function update_surface_temperature!(pos::Dims{2}, model::DaisyWorld) end local_heating = absorbed_luminosity > 0 ? 72 * log(absorbed_luminosity) + 80 : 80 T0 = model[ids[1]].temperature - model[ids[1]].temperature = (T0 + local_heating) / 2 + return model[ids[1]].temperature = (T0 + local_heating) / 2 end function diffuse_temperature!(pos::Dims{2}, model::DaisyWorld) @@ -60,7 +60,7 @@ function diffuse_temperature!(pos::Dims{2}, model::DaisyWorld) ids = nearby_ids(pos, model) meantemp = sum(model[i].temperature for i in ids if model[i] isa Land) / 8 land = model[ids_in_position(pos, model)[1]] - land.temperature = (1 - ratio) * land.temperature + ratio * meantemp + return land.temperature = (1 - ratio) * land.temperature + ratio * meantemp end function propagate!(pos::Dims{2}, model::DaisyWorld) @@ -88,7 +88,7 @@ end function agent_step!(agent::Daisy, model::DaisyWorld) agent.age += 1 - agent.age >= model.max_age && kill_agent!(agent, model) + return agent.age >= model.max_age && kill_agent!(agent, model) end agent_step!(agent::Land, model::DaisyWorld) = nothing @@ -100,7 +100,7 @@ function model_step!(model) propagate!(p, model) end model.tick += 1 - solar_activity!(model) + return solar_activity!(model) end function solar_activity!(model::DaisyWorld) @@ -122,45 +122,40 @@ end # automatically through our model. function daisyworld(; - griddims = (30, 30), - max_age = 25, - init_white = 0.2, - init_black = 0.2, - albedo_white = 0.75, - albedo_black = 0.25, + griddims=(30, 30), + max_age=25, + init_white=0.2, + init_black=0.2, + albedo_white=0.75, + albedo_black=0.25, ## Surface albedo measurements are complicated for our satellites perhaps - surface_albedo = 0.4 ± 0.15, + surface_albedo=0.4 ± 0.15, ## Measurements from the sun are generally stable, but fluctuate around 10% - solar_change = 0.005 ± 0.002, - solar_luminosity = 1.0 ± 0.1, - scenario = :default, + solar_change=0.005 ± 0.002, + solar_luminosity=1.0 ± 0.1, + scenario=:default, ) - space = GridSpace(griddims) properties = @dict max_age surface_albedo solar_luminosity solar_change scenario properties[:tick] = 0 daisysched(model) = [a.id for a in allagents(model) if a isa Daisy] model = ABM( - Union{Daisy,Land}, - space; - scheduler = daisysched, - properties = properties, - warn = false, + Union{Daisy,Land}, space; scheduler=daisysched, properties=properties, warn=false ) ## An uncertain initial temperature, solely for type stability fill_space!(Land, model, 0.0 ± 0.0) grid = collect(positions(model)) num_positions = prod(griddims) - white_positions = - StatsBase.sample(grid, Int(init_white * num_positions); replace = false) + white_positions = StatsBase.sample(grid, Int(init_white * num_positions); replace=false) for wp in white_positions wd = Daisy(nextid(model), wp, :white, rand(model.rng, 0:max_age), albedo_white) add_agent_pos!(wd, model) end allowed = setdiff(grid, white_positions) - black_positions = - StatsBase.sample(allowed, Int(init_black * num_positions); replace = false) + black_positions = StatsBase.sample( + allowed, Int(init_black * num_positions); replace=false + ) for bp in black_positions wd = Daisy(nextid(model), bp, :black, rand(model.rng, 0:max_age), albedo_black) add_agent_pos!(wd, model) @@ -193,55 +188,54 @@ mdata = [:solar_luminosity] # And now the simulation Random.seed!(19) # hide -model = daisyworld(scenario = :ramp) -agent_df, model_df = - run!(model, agent_step!, model_step!, 1000; adata = adata, mdata = mdata) - -f = Figure(resolution = (600, 800)) -ax = f[1, 1] = Axis(f, ylabel = "Daisy count", title = "Daisyworld Analysis") -lb = lines!(ax, agent_df.step, agent_df.count_black_daisies, linewidth = 2, color = :blue) -lw = lines!(ax, agent_df.step, agent_df.count_white_daisies, linewidth = 2, color = :red) +model = daisyworld(; scenario=:ramp) +agent_df, model_df = run!(model, agent_step!, model_step!, 1000; adata=adata, mdata=mdata) + +f = Figure(; resolution=(600, 800)) +ax = f[1, 1] = Axis(f; ylabel="Daisy count", title="Daisyworld Analysis") +lb = lines!(ax, agent_df.step, agent_df.count_black_daisies; linewidth=2, color=:blue) +lw = lines!(ax, agent_df.step, agent_df.count_white_daisies; linewidth=2, color=:red) leg = f[1, 1] = Legend( f, [lb, lw], - ["black", "white"], - tellheight = false, - tellwidth = false, - halign = :right, - valign = :top, - margin = (10, 10, 10, 10), + ["black", "white"]; + tellheight=false, + tellwidth=false, + halign=:right, + valign=:top, + margin=(10, 10, 10, 10), ) -ax2 = f[2, 1] = Axis(f, ylabel = "Temperature") +ax2 = f[2, 1] = Axis(f; ylabel="Temperature") highband = Measurements.value.(agent_df[!, dataname(adata[3])]) + Measurements.uncertainty.(agent_df[!, dataname(adata[3])]) lowband = Measurements.value.(agent_df[!, dataname(adata[3])]) - Measurements.uncertainty.(agent_df[!, dataname(adata[3])]) -band!(ax2, agent_df.step, lowband, highband, color = (:steelblue, 0.5)) +band!(ax2, agent_df.step, lowband, highband; color=(:steelblue, 0.5)) lines!( ax2, agent_df.step, - Measurements.value.(agent_df[!, dataname(adata[3])]), - linewidth = 2, - color = :blue, + Measurements.value.(agent_df[!, dataname(adata[3])]); + linewidth=2, + color=:blue, ) -ax3 = f[3, 1] = Axis(f, ylabel = "Luminosity") +ax3 = f[3, 1] = Axis(f; ylabel="Luminosity") highband = Measurements.value.(model_df.solar_luminosity) + Measurements.uncertainty.(model_df.solar_luminosity) lowband = Measurements.value.(model_df.solar_luminosity) - Measurements.uncertainty.(model_df.solar_luminosity) -band!(ax3, agent_df.step, lowband, highband, color = (:steelblue, 0.5)) +band!(ax3, agent_df.step, lowband, highband; color=(:steelblue, 0.5)) lines!( ax3, agent_df.step, - Measurements.value.(model_df.solar_luminosity), - linewidth = 2, - color = :blue, + Measurements.value.(model_df.solar_luminosity); + linewidth=2, + color=:blue, ) f diff --git a/examples/optim.jl b/examples/optim.jl index bfb6c4f463..dfed6f770e 100644 --- a/examples/optim.jl +++ b/examples/optim.jl @@ -283,4 +283,3 @@ # We now have a set of parameters to strive towards in the real world. Insights # such as these assist us to enact countermeasures like social distancing to mitigate # infection risks. - diff --git a/examples/predator_prey.jl b/examples/predator_prey.jl index 2a446c25c5..50a018e4a8 100644 --- a/examples/predator_prey.jl +++ b/examples/predator_prey.jl @@ -67,37 +67,34 @@ end # directions. function initialize_model(; - n_sheep = 100, - n_wolves = 50, - dims = (20, 20), - regrowth_time = 30, - Δenergy_sheep = 4, - Δenergy_wolf = 20, - sheep_reproduce = 0.04, - wolf_reproduce = 0.05, - seed = 23182, - ) - + n_sheep=100, + n_wolves=50, + dims=(20, 20), + regrowth_time=30, + Δenergy_sheep=4, + Δenergy_wolf=20, + sheep_reproduce=0.04, + wolf_reproduce=0.05, + seed=23182, +) rng = MersenneTwister(seed) - space = GridSpace(dims, periodic = true) + space = GridSpace(dims; periodic=true) ## Model properties contain the grass as two arrays: whether it is fully grown ## and the time to regrow. Also have static parameter `regrowth_time`. ## Notice how the properties are a `NamedTuple` to ensure type stability. properties = ( - fully_grown = falses(dims), - countdown = zeros(Int, dims), - regrowth_time = regrowth_time, + fully_grown=falses(dims), countdown=zeros(Int, dims), regrowth_time=regrowth_time ) - model = ABM(Union{Sheep, Wolf}, space; - properties, rng, scheduler = Schedulers.randomly, warn = false + model = ABM( + Union{Sheep,Wolf}, space; properties, rng, scheduler=Schedulers.randomly, warn=false ) ## Add agents for _ in 1:n_sheep - energy = rand(model.rng, 1:(Δenergy_sheep*2)) - 1 + energy = rand(model.rng, 1:(Δenergy_sheep * 2)) - 1 add_agent!(Sheep, model, energy, sheep_reproduce, Δenergy_sheep) end for _ in 1:n_wolves - energy = rand(model.rng, 1:(Δenergy_wolf*2)) - 1 + energy = rand(model.rng, 1:(Δenergy_wolf * 2)) - 1 add_agent!(Wolf, model, energy, wolf_reproduce, Δenergy_wolf) end ## Add grass with random initial growth @@ -126,7 +123,7 @@ function sheepwolf_step!(sheep::Sheep, model) sheep.energy -= 1 if sheep.energy < 0 kill_agent!(sheep, model) - return + return nothing end eat!(sheep, model) if rand(model.rng) ≤ sheep.reproduction_prob @@ -139,7 +136,7 @@ function sheepwolf_step!(wolf::Wolf, model) wolf.energy -= 1 if wolf.energy < 0 kill_agent!(wolf, model) - return + return nothing end ## If there is any sheep on this grid cell, it's dinner time! dinner = first_sheep_in_position(wolf.pos, model) @@ -152,11 +149,9 @@ end function first_sheep_in_position(pos, model) ids = ids_in_position(pos, model) j = findfirst(id -> model[id] isa Sheep, ids) - isnothing(j) ? nothing : model[ids[j]]::Sheep + return isnothing(j) ? nothing : model[ids[j]]::Sheep end - - # Sheep and wolves have separate `eat!` functions. If a sheep eats grass, it will acquire # additional energy and the grass will not be available for consumption until regrowth time # has elapsed. If a wolf eats a sheep, the sheep dies and the wolf acquires more energy. @@ -165,13 +160,13 @@ function eat!(sheep::Sheep, model) sheep.energy += sheep.Δenergy model.fully_grown[sheep.pos...] = false end - return + return nothing end function eat!(wolf::Wolf, sheep::Sheep, model) kill_agent!(sheep, model) wolf.energy += wolf.Δenergy - return + return nothing end # Sheep and wolves share a common reproduction method. Reproduction has a cost of 1/2 the @@ -182,7 +177,7 @@ function reproduce!(agent::A, model) where {A} id = nextid(model) offspring = A(id, agent.pos, agent.energy, agent.reproduction_prob, agent.Δenergy) add_agent_pos!(offspring, model) - return + return nothing end # The behavior of grass function differently. If it is fully grown, it is consumable. @@ -211,32 +206,31 @@ CairoMakie.activate!() # hide # To view our starting population, we can build an overview plot using [`abmplot`](@ref). # We define the plotting details for the wolves and sheep: -offset(a) = a isa Sheep ? (-0.1, -0.1*rand()) : (+0.1, +0.1*rand()) +offset(a) = a isa Sheep ? (-0.1, -0.1 * rand()) : (+0.1, +0.1 * rand()) ashape(a) = a isa Sheep ? :circle : :utriangle acolor(a) = a isa Sheep ? RGBAf(1.0, 1.0, 1.0, 0.8) : RGBAf(0.2, 0.2, 0.3, 0.8) # and instruct [`abmplot`](@ref) how to plot grass as a heatmap: grasscolor(model) = model.countdown ./ model.regrowth_time # and finally define a colormap for the grass: -heatkwargs = (colormap = [:brown, :green], colorrange = (0, 1)) +heatkwargs = (colormap=[:brown, :green], colorrange=(0, 1)) # and put everything together and give it to [`abmplot`](@ref) plotkwargs = (; - ac = acolor, - as = 25, - am = ashape, + ac=acolor, + as=25, + am=ashape, offset, - scatterkwargs = (strokewidth = 1.0, strokecolor = :black), - heatarray = grasscolor, - heatkwargs = heatkwargs, + scatterkwargs=(strokewidth=1.0, strokecolor=:black), + heatarray=grasscolor, + heatkwargs=heatkwargs, ) sheepwolfgrass = initialize_model() -fig, ax, abmobs = abmplot(sheepwolfgrass; - agent_step! = sheepwolf_step!, - model_step! = grass_step!, -plotkwargs...) +fig, ax, abmobs = abmplot( + sheepwolfgrass; (agent_step!)=sheepwolf_step!, (model_step!)=grass_step!, plotkwargs... +) fig # Now, lets run the simulation and collect some data. Define datacollection: @@ -255,13 +249,13 @@ adf, mdf = run!(sheepwolfgrass, sheepwolf_step!, grass_step!, steps; adata, mdat # The few remaining sheep reproduce and gradually reach an # equilibrium that can be supported by the amount of available grass. function plot_population_timeseries(adf, mdf) - figure = Figure(resolution = (600, 400)) - ax = figure[1, 1] = Axis(figure; xlabel = "Step", ylabel = "Population") - sheepl = lines!(ax, adf.step, adf.count_sheep, color = :cornsilk4) - wolfl = lines!(ax, adf.step, adf.count_wolf, color = RGBAf(0.2, 0.2, 0.3)) - grassl = lines!(ax, mdf.step, mdf.count_grass, color = :green) + figure = Figure(; resolution=(600, 400)) + ax = figure[1, 1] = Axis(figure; xlabel="Step", ylabel="Population") + sheepl = lines!(ax, adf.step, adf.count_sheep; color=:cornsilk4) + wolfl = lines!(ax, adf.step, adf.count_wolf; color=RGBAf(0.2, 0.2, 0.3)) + grassl = lines!(ax, mdf.step, mdf.count_grass; color=:green) figure[1, 2] = Legend(figure, [sheepl, wolfl, grassl], ["Sheep", "Wolves", "Grass"]) - figure + return figure end plot_population_timeseries(adf, mdf) @@ -270,17 +264,17 @@ plot_population_timeseries(adf, mdf) # find an equilibrium # %% #src stable_params = (; - n_sheep = 140, - n_wolves = 20, - dims = (30, 30), - Δenergy_sheep = 5, - sheep_reproduce = 0.31, - wolf_reproduce = 0.06, - Δenergy_wolf = 30, - seed = 71758, + n_sheep=140, + n_wolves=20, + dims=(30, 30), + Δenergy_sheep=5, + sheep_reproduce=0.31, + wolf_reproduce=0.06, + Δenergy_wolf=30, + seed=71758, ) -sheepwolfgrass = initialize_model(;stable_params...) +sheepwolfgrass = initialize_model(; stable_params...) adf, mdf = run!(sheepwolfgrass, sheepwolf_step!, grass_step!, 2000; adata, mdata) plot_population_timeseries(adf, mdf) @@ -291,16 +285,16 @@ plot_population_timeseries(adf, mdf) # ## Video # Given that we have defined plotting functions, making a video is as simple as -sheepwolfgrass = initialize_model(;stable_params...) +sheepwolfgrass = initialize_model(; stable_params...) abmvideo( "sheepwolf.mp4", sheepwolfgrass, sheepwolf_step!, grass_step!; - frames = 100, - framerate = 8, - title = "Sheep Wolf Grass", + frames=100, + framerate=8, + title="Sheep Wolf Grass", plotkwargs..., ) diff --git a/examples/rabbit_fox_hawk.jl b/examples/rabbit_fox_hawk.jl index cf516b341a..5f9c34cd5f 100644 --- a/examples/rabbit_fox_hawk.jl +++ b/examples/rabbit_fox_hawk.jl @@ -21,7 +21,7 @@ # and reproduce. Eating food (grass or rabbits) replenishes `energy` by a fixed amount. using Agents, Agents.Pathfinding using Random -import ImageMagick +using ImageMagick: ImageMagick using FileIO: load @agent Animal ContinuousAgent{3} begin @@ -56,29 +56,28 @@ eunorm(vec) = √sum(vec .^ 2) # with the specified heightmap and containing the specified number of rabbits, foxes and hawks. function initialize_model( - heightmap_url = - "https://raw.githubusercontent.com/JuliaDynamics/" * - "JuliaDynamics/master/videos/agents/rabbit_fox_hawk_heightmap.png", - water_level = 8, - grass_level = 20, - mountain_level = 35; - n_rabbits = 160, ## initial number of rabbits - n_foxes = 30, ## initial number of foxes - n_hawks = 30, ## initial number of hawks - Δe_grass = 25, ## energy gained from eating grass - Δe_rabbit = 30, ## energy gained from eating one rabbit - rabbit_repr = 0.06, ## probability for a rabbit to (asexually) reproduce at any step - fox_repr = 0.03, ## probability for a fox to (asexually) reproduce at any step - hawk_repr = 0.02, ## probability for a hawk to (asexually) reproduce at any step - rabbit_vision = 6, ## how far rabbits can see grass and spot predators - fox_vision = 10, ## how far foxes can see rabbits to hunt - hawk_vision = 15, ## how far hawks can see rabbits to hunt - rabbit_speed = 1.3, ## movement speed of rabbits - fox_speed = 1.1, ## movement speed of foxes - hawk_speed = 1.2, ## movement speed of hawks - regrowth_chance = 0.03, ## probability that a patch of grass regrows at any step - dt = 0.1, ## discrete timestep each iteration of the model - seed = 42, ## seed for random number generator + heightmap_url="https://raw.githubusercontent.com/JuliaDynamics/" * + "JuliaDynamics/master/videos/agents/rabbit_fox_hawk_heightmap.png", + water_level=8, + grass_level=20, + mountain_level=35; + n_rabbits=160, ## initial number of rabbits + n_foxes=30, ## initial number of foxes + n_hawks=30, ## initial number of hawks + Δe_grass=25, ## energy gained from eating grass + Δe_rabbit=30, ## energy gained from eating one rabbit + rabbit_repr=0.06, ## probability for a rabbit to (asexually) reproduce at any step + fox_repr=0.03, ## probability for a fox to (asexually) reproduce at any step + hawk_repr=0.02, ## probability for a hawk to (asexually) reproduce at any step + rabbit_vision=6, ## how far rabbits can see grass and spot predators + fox_vision=10, ## how far foxes can see rabbits to hunt + hawk_vision=15, ## how far hawks can see rabbits to hunt + rabbit_speed=1.3, ## movement speed of rabbits + fox_speed=1.1, ## movement speed of foxes + hawk_speed=1.2, ## movement speed of hawks + regrowth_chance=0.03, ## probability that a patch of grass regrows at any step + dt=0.1, ## discrete timestep each iteration of the model + seed=42, ## seed for random number generator ) ## Download and load the heightmap. The grayscale value is converted to `Float64` and @@ -93,11 +92,11 @@ function initialize_model( for i in 1:dims[1], j in 1:dims[2] ## land animals can only walk on top of the terrain between water_level and grass_level if water_level < heightmap[i, j] < grass_level - land_walkmap[i, j, heightmap[i, j]+1] = true + land_walkmap[i, j, heightmap[i, j] + 1] = true end ## air animals can fly at any height upto mountain_level if heightmap[i, j] < mountain_level - air_walkmap[i, j, (heightmap[i, j]+1):mountain_level] .= true + air_walkmap[i, j, (heightmap[i, j] + 1):mountain_level] .= true end end @@ -106,36 +105,37 @@ function initialize_model( ## Note that the dimensions of the space do not have to correspond to the dimensions ## of the pathfinder. Discretisation is handled by the pathfinding methods - space = ContinuousSpace((100., 100., 50.); periodic = false) + space = ContinuousSpace((100.0, 100.0, 50.0); periodic=false) ## Generate an array of random numbers, and threshold it by the probability of grass growing ## at that location. Although this causes grass to grow below `water_level`, it is ## effectively ignored by `land_walkmap` grass = BitArray( - rand(rng, dims[1:2]...) .< ((grass_level .- heightmap) ./ (grass_level - water_level)), + rand(rng, dims[1:2]...) .< + ((grass_level .- heightmap) ./ (grass_level - water_level)), ) properties = ( ## The pathfinder for rabbits and foxes - landfinder = AStar(space; walkmap = land_walkmap), + landfinder=AStar(space; walkmap=land_walkmap), ## The pathfinder for hawks - airfinder = AStar(space; walkmap = air_walkmap, cost_metric = MaxDistance{3}()), - Δe_grass = Δe_grass, - Δe_rabbit = Δe_rabbit, - rabbit_repr = rabbit_repr, - fox_repr = fox_repr, - hawk_repr = hawk_repr, - rabbit_vision = rabbit_vision, - fox_vision = fox_vision, - hawk_vision = hawk_vision, - rabbit_speed = rabbit_speed, - fox_speed = fox_speed, - hawk_speed = hawk_speed, - heightmap = heightmap, - grass = grass, - regrowth_chance = regrowth_chance, - water_level = water_level, - grass_level = grass_level, - dt = dt, + airfinder=AStar(space; walkmap=air_walkmap, cost_metric=MaxDistance{3}()), + Δe_grass=Δe_grass, + Δe_rabbit=Δe_rabbit, + rabbit_repr=rabbit_repr, + fox_repr=fox_repr, + hawk_repr=hawk_repr, + rabbit_vision=rabbit_vision, + fox_vision=fox_vision, + hawk_vision=hawk_vision, + rabbit_speed=rabbit_speed, + fox_speed=fox_speed, + hawk_speed=hawk_speed, + heightmap=heightmap, + grass=grass, + regrowth_chance=regrowth_chance, + water_level=water_level, + grass_level=grass_level, + dt=dt, ) model = ABM(Animal, space; rng, properties) @@ -145,9 +145,9 @@ function initialize_model( add_agent_pos!( Rabbit( nextid(model), ## Using `nextid` prevents us from having to manually keep track - ## of animal IDs + ## of animal IDs random_walkable(model, model.landfinder), - rand(model.rng, Δe_grass:2Δe_grass), + rand(model.rng, Δe_grass:(2Δe_grass)), ), model, ) @@ -157,7 +157,7 @@ function initialize_model( Fox( nextid(model), random_walkable(model, model.landfinder), - rand(model.rng, Δe_rabbit:2Δe_rabbit), + rand(model.rng, Δe_rabbit:(2Δe_rabbit)), ), model, ) @@ -167,7 +167,7 @@ function initialize_model( Hawk( nextid(model), random_walkable(model, model.airfinder), - rand(model.rng, Δe_rabbit:2Δe_rabbit), + rand(model.rng, Δe_rabbit:(2Δe_rabbit)), ), model, ) @@ -216,38 +216,42 @@ function rabbit_step!(rabbit, model) ## All animals die if their energy reaches 0 if rabbit.energy <= 0 kill_agent!(rabbit, model, model.landfinder) - return + return nothing end ## Get a list of positions of all nearby predators predators = [ x.pos for x in nearby_agents(rabbit, model, model.rabbit_vision) if - x.type == :fox || x.type == :hawk - ] + x.type == :fox || x.type == :hawk + ] ## If the rabbit sees a predator and isn't already moving somewhere if !isempty(predators) && is_stationary(rabbit, model.landfinder) ## Try and get an ideal direction away from predators - direction = (0., 0., 0.) + direction = (0.0, 0.0, 0.0) for predator in predators ## Get the direction away from the predator away_direction = (rabbit.pos .- predator) ## In case there is already a predator at our location, moving anywhere is ## moving away from it, so it doesn't contribute to `direction` - all(away_direction .≈ 0.) && continue + all(away_direction .≈ 0.0) && continue ## Add this to the overall direction, scaling inversely with distance. ## As a result, closer predators contribute more to the direction to move in - direction = direction .+ away_direction ./ eunorm(away_direction) ^ 2 + direction = direction .+ away_direction ./ eunorm(away_direction)^2 end ## If the only predator is right on top of the rabbit - if all(direction .≈ 0.) + if all(direction .≈ 0.0) ## Move anywhere - chosen_position = random_walkable(rabbit.pos, model, model.landfinder, model.rabbit_vision) + chosen_position = random_walkable( + rabbit.pos, model, model.landfinder, model.rabbit_vision + ) else ## Normalize the resultant direction, and get the ideal position to move it direction = direction ./ eunorm(direction) ## Move to a random position in the general direction of away from predators - position = rabbit.pos .+ direction .* (model.rabbit_vision / 2.) - chosen_position = random_walkable(position, model, model.landfinder, model.rabbit_vision / 2.) + position = rabbit.pos .+ direction .* (model.rabbit_vision / 2.0) + chosen_position = random_walkable( + position, model, model.landfinder, model.rabbit_vision / 2.0 + ) end plan_route!(rabbit, chosen_position, model.landfinder) end @@ -261,12 +265,12 @@ function rabbit_step!(rabbit, model) plan_route!( rabbit, random_walkable(rabbit.pos, model, model.landfinder, model.rabbit_vision), - model.landfinder + model.landfinder, ) end ## Move along the route planned above - move_along_route!(rabbit, model, model.landfinder, model.rabbit_speed, model.dt) + return move_along_route!(rabbit, model, model.landfinder, model.rabbit_speed, model.dt) end # Foxes hunt for rabbits, and eat rabbits within a unit radius of its position. @@ -279,14 +283,13 @@ function fox_step!(fox, model) fox.energy += model.Δe_rabbit end - ## The energy cost at each step corresponds to the amount of time that has passed ## since the last step fox.energy -= model.dt ## All animals die once their energy reaches 0 if fox.energy <= 0 kill_agent!(fox, model, model.landfinder) - return + return nothing end ## Random chance to reproduce every step @@ -309,7 +312,7 @@ function fox_step!(fox, model) end end - move_along_route!(fox, model, model.landfinder, model.fox_speed, model.dt) + return move_along_route!(fox, model, model.landfinder, model.fox_speed, model.dt) end # Hawks function similarly to foxes, except they can also fly. They dive down for prey and @@ -323,7 +326,7 @@ function hawk_step!(hawk, model) kill_agent!(rand(model.rng, food), model, model.airfinder) hawk.energy += model.Δe_rabbit ## Fly back up - plan_route!(hawk, hawk.pos .+ (0., 0., 7.), model.airfinder) + plan_route!(hawk, hawk.pos .+ (0.0, 0.0, 7.0), model.airfinder) end ## The rest of the stepping function is similar to that of foxes, except hawks use a @@ -331,13 +334,15 @@ function hawk_step!(hawk, model) hawk.energy -= model.dt if hawk.energy <= 0 kill_agent!(hawk, model, model.airfinder) - return + return nothing end rand(model.rng) <= model.hawk_repr * model.dt && reproduce!(hawk, model) if is_stationary(hawk, model.airfinder) - prey = [x for x in nearby_agents(hawk, model, model.hawk_vision) if x.type == :rabbit] + prey = [ + x for x in nearby_agents(hawk, model, model.hawk_vision) if x.type == :rabbit + ] if isempty(prey) plan_route!( hawk, @@ -349,7 +354,7 @@ function hawk_step!(hawk, model) end end - move_along_route!(hawk, model, model.airfinder, model.hawk_speed, model.dt) + return move_along_route!(hawk, model, model.airfinder, model.hawk_speed, model.dt) end # This function is called when an animal reproduces. The animal loses half its energy, and @@ -357,7 +362,9 @@ end function reproduce!(animal, model) animal.energy = Float64(ceil(Int, animal.energy / 2)) - add_agent_pos!(Animal(nextid(model), animal.pos, v0, animal.type, animal.energy), model) + return add_agent_pos!( + Animal(nextid(model), animal.pos, v0, animal.type, animal.energy), model + ) end # The model stepping function simulates the growth of grass @@ -371,7 +378,7 @@ function model_step!(model) ) ## Grass regrows with a random probability, scaling with the amount of time passing ## each step of the model - growable .= rand(model.rng, length(growable)) .< model.regrowth_chance * model.dt + return growable .= rand(model.rng, length(growable)) .< model.regrowth_chance * model.dt end # ## Visualization @@ -398,12 +405,12 @@ animalcolor(a) = # colormap. Since the heightmap dimensions don't correspond to the dimensions of the space, # we explicitly provide ranges to specify where the heightmap should be plotted. function static_preplot!(ax, model) - surface!( + return surface!( ax, - (100/205):(100/205):100, - (100/205):(100/205):100, + (100 / 205):(100 / 205):100, + (100 / 205):(100 / 205):100, model.heightmap; - colormap = :terrain + colormap=:terrain, ) end diff --git a/examples/schelling.jl b/examples/schelling.jl index e6a7e26ee6..e7628f2033 100644 --- a/examples/schelling.jl +++ b/examples/schelling.jl @@ -31,7 +31,7 @@ # ## Creating a space using Agents -space = GridSpaceSingle((10, 10); periodic = false) +space = GridSpaceSingle((10, 10); periodic=false) # Notice that by default the `GridSpaceSingle` has `metric = Chebyshev()`, # which is what we want. # Agents existing in this type of space must have a position field that is a @@ -82,12 +82,7 @@ schelling = ABM(SchellingAgent, space; properties) # property `:group`, so that all agents of group 1 act first. # We would then use the scheduler [`Schedulers.ByProperty`](@ref) like so: -schelling2 = ABM( - SchellingAgent, - space; - properties, - scheduler = Schedulers.ByProperty(:group), -) +schelling2 = ABM(SchellingAgent, space; properties, scheduler=Schedulers.ByProperty(:group)) # Notice that `Schedulers.ByProperty` accepts an argument and returns a struct, # which is why we didn't just give `Schedulers.ByProperty` to `scheduler`. @@ -112,7 +107,6 @@ nagents(schelling) # We can obtain the created and added agent, that got assigned the ID 2, like so agent = schelling[2] - # ## Using an `UnkillableABM` # We know that the number of agents in the model never change. @@ -141,13 +135,12 @@ schelling = UnkillableABM(SchellingAgent, space; properties) # it will be of further use in [`paramscan`](@ref) below. using Random # for reproducibility -function initialize(; total_agents = 320, griddims = (20, 20), min_to_be_happy = 3, seed = 125) - space = GridSpaceSingle(griddims, periodic = false) +function initialize(; total_agents=320, griddims=(20, 20), min_to_be_happy=3, seed=125) + space = GridSpaceSingle(griddims; periodic=false) properties = Dict(:min_to_be_happy => min_to_be_happy) rng = Random.Xoshiro(seed) model = UnkillableABM( - SchellingAgent, space; - properties, rng, scheduler = Schedulers.Randomly() + SchellingAgent, space; properties, rng, scheduler=Schedulers.Randomly() ) ## populate the model with agents, adding equal amount of the two types of agents ## at random positions in the model @@ -160,7 +153,6 @@ end model = initialize() - # ## Defining a step function # Finally, we define a _step_ function to determine what happens to an @@ -190,7 +182,7 @@ function agent_step!(agent, model) agent.mood = false move_agent_single!(agent, model) end - return + return nothing end # When defining `agent_step!`, we used some of the built-in functions of Agents.jl, @@ -228,7 +220,7 @@ CairoMakie.activate!() # hide groupcolor(a) = a.group == 1 ? :blue : :orange groupmarker(a) = a.group == 1 ? :circle : :rect -figure, _ = abmplot(model; ac = groupcolor, am = groupmarker, as = 10) +figure, _ = abmplot(model; ac=groupcolor, am=groupmarker, as=10) figure # returning the figure displays it # ## Animating the evolution @@ -239,10 +231,15 @@ figure # returning the figure displays it model = initialize(); abmvideo( - "schelling.mp4", model, agent_step!; - ac = groupcolor, am = groupmarker, as = 10, - framerate = 4, frames = 20, - title = "Schelling's segregation model" + "schelling.mp4", + model, + agent_step!; + ac=groupcolor, + am=groupmarker, + as=10, + framerate=4, + frames=20, + title="Schelling's segregation model", ) # ```@raw html @@ -299,7 +296,7 @@ parange = Dict(:min_to_be_happy => 0:8) adata = [(:mood, sum), (x, mean)] alabels = ["happy", "avg. x"] -model = initialize(; total_agents = 300) # fresh model, noone happy +model = initialize(; total_agents=300) # fresh model, noone happy # ```julia # using GLMakie # using a different plotting backend that enables interactive plots @@ -330,10 +327,10 @@ model = initialize(; total_agents = 300) # fresh model, noone happy # First, let's create a model with 200 agents and run it for 40 iterations. @eval Main __atexample__named__schelling = $(@__MODULE__) # hide -model = initialize(total_agents = 200, min_to_be_happy = 5, seed = 42) +model = initialize(; total_agents=200, min_to_be_happy=5, seed=42) run!(model, agent_step!, 40) -figure, _ = abmplot(model; ac = groupcolor, am = groupmarker, as = 10) +figure, _ = abmplot(model; ac=groupcolor, am=groupmarker, as=10) figure # Most of the agents have settled happily. Now, let's save the model. @@ -341,11 +338,11 @@ AgentsIO.save_checkpoint("schelling.jld2", model) # Note that we can now leave the REPL, and come back later to run the model, # right from where we left off. -model = AgentsIO.load_checkpoint("schelling.jld2"; scheduler = Schedulers.Randomly()) +model = AgentsIO.load_checkpoint("schelling.jld2"; scheduler=Schedulers.Randomly()) # Since functions are not saved, the scheduler has to be passed while loading # the model. Let's now verify that we loaded back exactly what we saved. -figure, _ = abmplot(model; ac = groupcolor, am = groupmarker, as = 10) +figure, _ = abmplot(model; ac=groupcolor, am=groupmarker, as=10) figure # For starters, let's see what happens if we add 100 more agents of group 1 @@ -355,19 +352,19 @@ for i in 1:100 end # Let's see what our model looks like now. -figure, _ = abmplot(model; ac = groupcolor, am = groupmarker, as = 10) +figure, _ = abmplot(model; ac=groupcolor, am=groupmarker, as=10) figure # And then run it for 40 iterations. run!(model, agent_step!, 40) -figure, _ = abmplot(model; ac = groupcolor, am = groupmarker, as = 10) +figure, _ = abmplot(model; ac=groupcolor, am=groupmarker, as=10) figure # It looks like the agents eventually cluster again. What if the agents are of a new group? # We can start by loading the model back in from the file, thus resetting the # changes we made. -model = AgentsIO.load_checkpoint("schelling.jld2"; scheduler = Schedulers.Randomly()) +model = AgentsIO.load_checkpoint("schelling.jld2"; scheduler=Schedulers.Randomly()) for i in 1:100 agent = SchellingAgent(nextid(model), (1, 1), false, 3) @@ -379,13 +376,13 @@ end groupcolor(a) = (:blue, :orange, :green)[a.group] groupmarker(a) = (:circle, :rect, :cross)[a.group] -figure, _ = abmplot(model; ac = groupcolor, am = groupmarker, as = 10) +figure, _ = abmplot(model; ac=groupcolor, am=groupmarker, as=10) figure # The new agents are scattered randomly, as expected. Now let's run the model. run!(model, agent_step!, 40) -figure, _ = abmplot(model; ac = groupcolor, am = groupmarker, as = 10) +figure, _ = abmplot(model; ac=groupcolor, am=groupmarker, as=10) figure # The new agents also form their own clusters, despite being completely scattered. @@ -400,7 +397,7 @@ rm("schelling.jld2") # hide # To that end we use the [`ensemblerun!`](@ref) function. # The function accepts a `Vector` of ABMs, each (typically) initialized with a different # seed and/or agent distribution. For example we can do -models = [initialize(seed = x) for x in rand(UInt8, 3)]; +models = [initialize(; seed=x) for x in rand(UInt8, 3)]; # and then adf, = ensemblerun!(models, agent_step!, dummystep, 5; adata) @@ -452,7 +449,7 @@ parameters = Dict( :griddims => (20, 20), # not Vector = not expanded ) -adf, _ = paramscan(parameters, initialize; adata, agent_step!, n = 3) +adf, _ = paramscan(parameters, initialize; adata, agent_step!, n=3) adf # We nicely see that the larger `:min_to_be_happy` is, the slower the convergence to diff --git a/examples/schoolyard.jl b/examples/schoolyard.jl index 8bd8aa1381..8ff4ada36e 100644 --- a/examples/schoolyard.jl +++ b/examples/schoolyard.jl @@ -47,24 +47,24 @@ const Student = ContinuousAgent{2} # ## Initialising the model function schoolyard(; - numStudents = 50, - teacher_attractor = 0.15, - noise = 0.1, - max_force = 1.7, - spacing = 4.0, - seed = 6998, - velocity = (0, 0), + numStudents=50, + teacher_attractor=0.15, + noise=0.1, + max_force=1.7, + spacing=4.0, + seed=6998, + velocity=(0, 0), ) model = ABM( Student, - ContinuousSpace((100, 100), spacing; periodic = false); - properties = Dict( + ContinuousSpace((100, 100), spacing; periodic=false); + properties=Dict( :teacher_attractor => teacher_attractor, :noise => noise, :buddies => SimpleWeightedDiGraph(numStudents), :max_force => max_force, ), - rng = MersenneTwister(seed) + rng=MersenneTwister(seed), ) for student in 1:numStudents ## Students begin near the school building @@ -77,7 +77,7 @@ function schoolyard(; foe = rand(model.rng, filter(s -> s != student, 1:numStudents)) add_edge!(model.buddies, student, foe, -rand(model.rng)) end - model + return model end # Our model contains the `buddies` property, which is our Graphs.jl directed, weighted graph. @@ -122,7 +122,7 @@ function agent_step!(student, model) ## Add all forces together to assign the students next position new_pos = student.pos .+ noise .+ teacher .+ network_force - move_agent!(student, new_pos, model) + return move_agent!(student, new_pos, model) end # Applying the rules for movement is relatively simple. For the network specifically, @@ -145,15 +145,19 @@ using CairoMakie CairoMakie.activate!() # hide function static_preplot!(ax, model) - obj = CairoMakie.scatter!([50 50]; color = :red) # Show position of teacher + obj = CairoMakie.scatter!([50 50]; color=:red) # Show position of teacher CairoMakie.hidedecorations!(ax) # hide tick labels etc. - CairoMakie.translate!(obj, 0, 0, 5) # be sure that the teacher will be above students + return CairoMakie.translate!(obj, 0, 0, 5) # be sure that the teacher will be above students end abmvideo( - "schoolyard.mp4", model, agent_step!, dummystep; - framerate = 15, frames = 40, - title = "Playgound dynamics", + "schoolyard.mp4", + model, + agent_step!, + dummystep; + framerate=15, + frames=40, + title="Playgound dynamics", static_preplot!, ) diff --git a/examples/sir.jl b/examples/sir.jl index 7a335b3547..b0d447e225 100644 --- a/examples/sir.jl +++ b/examples/sir.jl @@ -58,25 +58,24 @@ function model_initiation(; migration_rates, β_und, β_det, - infection_period = 30, - reinfection_probability = 0.05, - detection_time = 14, - death_rate = 0.02, - Is = [zeros(Int, length(Ns) - 1)..., 1], - seed = 0, + infection_period=30, + reinfection_probability=0.05, + detection_time=14, + death_rate=0.02, + Is=[zeros(Int, length(Ns) - 1)..., 1], + seed=0, ) - rng = Xoshiro(seed) @assert length(Ns) == - length(Is) == - length(β_und) == - length(β_det) == - size(migration_rates, 1) "length of Ns, Is, and B, and number of rows/columns in migration_rates should be the same " + length(Is) == + length(β_und) == + length(β_det) == + size(migration_rates, 1) "length of Ns, Is, and B, and number of rows/columns in migration_rates should be the same " @assert size(migration_rates, 1) == size(migration_rates, 2) "migration_rates rates should be a square matrix" C = length(Ns) ## normalize migration_rates - migration_rates_sum = sum(migration_rates, dims = 2) + migration_rates_sum = sum(migration_rates; dims=2) for c in 1:C migration_rates[c, :] ./= migration_rates_sum[c] end @@ -127,14 +126,13 @@ using LinearAlgebra: diagind function create_params(; C, max_travel_rate, - infection_period = 30, - reinfection_probability = 0.05, - detection_time = 14, - death_rate = 0.02, - Is = [zeros(Int, C - 1)..., 1], - seed = 19, + infection_period=30, + reinfection_probability=0.05, + detection_time=14, + death_rate=0.02, + Is=[zeros(Int, C - 1)..., 1], + seed=19, ) - Random.seed!(seed) Ns = rand(50:5000, C) β_und = rand(0.3:0.02:0.6, C) @@ -166,7 +164,7 @@ function create_params(; return params end -params = create_params(C = 8, max_travel_rate = 0.01) +params = create_params(; C=8, max_travel_rate=0.01) model = model_initiation(; params...) # ## SIR Stepping functions @@ -177,7 +175,7 @@ function agent_step!(agent, model) migrate!(agent, model) transmit!(agent, model) update!(agent, model) - recover_or_die!(agent, model) + return recover_or_die!(agent, model) end function migrate!(agent, model) @@ -189,7 +187,7 @@ function migrate!(agent, model) end function transmit!(agent, model) - agent.status == :S && return + agent.status == :S && return nothing rate = if agent.days_infected < model.detection_time model.β_und[agent.pos] else @@ -197,15 +195,15 @@ function transmit!(agent, model) end n = rate * abs(randn(model.rng)) - n <= 0 && return + n <= 0 && return nothing for contactID in ids_in_position(agent, model) contact = model[contactID] if contact.status == :S || - (contact.status == :R && rand(model.rng) ≤ model.reinfection_probability) + (contact.status == :R && rand(model.rng) ≤ model.reinfection_probability) contact.status = :I n -= 1 - n <= 0 && return + n <= 0 && return nothing end end end @@ -239,17 +237,18 @@ fracs = lift(infected_fractions, abmobs.model) color = lift(fs -> [cgrad(:inferno)[f] for f in fs], fracs) title = lift( (s, m) -> "step = $(s), infected = $(round(Int, 100infected_fraction(m, allids(m))))%", - abmobs.s, abmobs.model + abmobs.s, + abmobs.model, ) # And lastly we use them to plot things in a figure -fig = Figure(resolution = (600, 400)) -ax = Axis(fig[1, 1]; title, xlabel = "City", ylabel = "Population") -barplot!(ax, model.Ns; strokecolor = :black, strokewidth = 1, color) +fig = Figure(; resolution=(600, 400)) +ax = Axis(fig[1, 1]; title, xlabel="City", ylabel="Population") +barplot!(ax, model.Ns; strokecolor=:black, strokewidth=1, color) fig # Now we can even make an animation of it -record(fig, "covid_evolution.mp4"; framerate = 5) do io +record(fig, "covid_evolution.mp4"; framerate=5) do io for j in 1:30 recordframe!(io) Agents.step!(abmobs, 1) @@ -278,7 +277,7 @@ nothing # hide model = model_initiation(; params...) to_collect = [(:status, f) for f in (infected, recovered, length)] -data, _ = run!(model, agent_step!, 100; adata = to_collect) +data, _ = run!(model, agent_step!, 100; adata=to_collect) data[1:10, :] # We now plot how quantities evolved in time to show @@ -286,12 +285,12 @@ data[1:10, :] N = sum(model.Ns) # Total initial population x = data.step -fig = Figure(resolution = (600, 400)) -ax = fig[1, 1] = Axis(fig, xlabel = "steps", ylabel = "log10(count)") -li = lines!(ax, x, log10.(data[:, aggname(:status, infected)]), color = :blue) -lr = lines!(ax, x, log10.(data[:, aggname(:status, recovered)]), color = :red) +fig = Figure(; resolution=(600, 400)) +ax = fig[1, 1] = Axis(fig; xlabel="steps", ylabel="log10(count)") +li = lines!(ax, x, log10.(data[:, aggname(:status, infected)]); color=:blue) +lr = lines!(ax, x, log10.(data[:, aggname(:status, recovered)]); color=:red) dead = log10.(N .- data[:, aggname(:status, length)]) -ld = lines!(ax, x, dead, color = :green) +ld = lines!(ax, x, dead; color=:green) Legend(fig[1, 2], [li, lr, ld], ["infected", "recovered", "dead"]) fig diff --git a/examples/zombies.jl b/examples/zombies.jl index 3d394c8e84..78d707d0d0 100644 --- a/examples/zombies.jl +++ b/examples/zombies.jl @@ -38,14 +38,14 @@ end # Unfortunately one of the population has turned and will begin infecting anyone who # comes close. -function initialise(; seed = 1234) +function initialise(; seed=1234) map_path = OSM.test_map() properties = Dict(:dt => 1 / 60) model = ABM( Zombie, OpenStreetMapSpace(map_path); - properties = properties, - rng = Random.MersenneTwister(seed) + properties=properties, + rng=Random.MersenneTwister(seed), ) for id in 1:100 @@ -53,7 +53,7 @@ function initialise(; seed = 1234) speed = rand(model.rng) * 5.0 + 2.0 # Random speed from 2-7kmph human = Zombie(id, start, false, speed) add_agent_pos!(human, model) - OSM.plan_random_route!(human, model; limit = 50) # try 50 times to find a random route + OSM.plan_random_route!(human, model; limit=50) # try 50 times to find a random route end ## We'll add patient zero at a specific (longitude, latitude) start = OSM.nearest_road((9.9351811, 51.5328328), model) @@ -78,7 +78,7 @@ function agent_step!(agent, model) if is_stationary(agent, model) && rand(model.rng) < 0.1 ## When stationary, give the agent a 10% chance of going somewhere else - OSM.plan_random_route!(agent, model; limit = 50) + OSM.plan_random_route!(agent, model; limit=50) ## Start on new route, moving the remaining distance move_along_route!(agent, model, distance_left) end @@ -87,7 +87,7 @@ function agent_step!(agent, model) ## Agents will be infected if they get too close (within 10m) to a zombie. map(i -> model[i].infected = true, nearby_ids(agent, model, 0.01)) end - return + return nothing end # ## Visualising the fall of humanity @@ -98,8 +98,16 @@ ac(agent) = agent.infected ? :green : :black as(agent) = agent.infected ? 10 : 8 model = initialise() -abmvideo("outbreak.mp4", model, agent_step!; -title = "Zombie outbreak", framerate = 15, frames = 200, as, ac) +abmvideo( + "outbreak.mp4", + model, + agent_step!; + title="Zombie outbreak", + framerate=15, + frames=200, + as, + ac, +) # ```@raw html #