Skip to content

Commit

Permalink
Add "wiring" capability (#42)
Browse files Browse the repository at this point in the history
* Add wires and wiring diagrams

* Add examples on wires

* Add Graphviz runner

* Fixing Agents.jl integration
  • Loading branch information
thevolatilebit authored Mar 17, 2024
1 parent 7ffcc45 commit ffc5761
Show file tree
Hide file tree
Showing 26 changed files with 2,218 additions and 116 deletions.
1 change: 1 addition & 0 deletions .github/workflows/Documenter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ts-graphviz/setup-graphviz@v2
- uses: julia-actions/setup-julia@latest
with:
version: '1.10'
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "AlgebraicAgents"
uuid = "f6eb0ae3-10fa-40e6-88dd-9006ba45093a"
version = "0.3.23"
version = "0.3.24"

[deps]
Crayons = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
Expand All @@ -12,8 +12,8 @@ Requires = "ae029012-a4dd-5104-9daa-d747884805df"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[compat]
julia = "1.9"
Crayons = "4.1"
Glob = "1.3"
MacroTools = "0.5"
Requires = "1.3"
julia = "1.8"
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DocumenterMarkdown = "997ab1e6-3595-5248-9280-8efb232c3433"
DrWatson = "634d3b9d-ee7a-5ddf-bec9-22491ea816e1"
Graphviz_jll = "3c863552-8265-54e4-a6dc-903eb78fde85"
LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
Expand Down
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ end
# Literate for tutorials
const literate_dir = joinpath(@__DIR__, "..", "tutorials")
const generated_dir = joinpath(@__DIR__, "src", "sketches")
const skip_dirs = ["traces"]
const skip_dirs = ["traces", "wires"]

for (root, dirs, files) in walkdir(literate_dir)
if any(occursin.(skip_dirs, root)) || startswith(root, "_")
Expand Down
223 changes: 223 additions & 0 deletions docs/sketches/agents/agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
```@meta
EditURL = "../../../../tutorials/agents/agents.jl"
```

# Agents.jl Integration

We instantiate an agent-based SIR model based on [Agents.jl: SIR model for the spread of COVID-19](https://juliadynamics.github.io/Agents.jl/stable/examples/sir/) make use of a SIR model constructor from an Agents.jl' [SIR model for the spread of COVID-19](https://juliadynamics.github.io/Agents.jl/stable/examples/sir/), and then we simulate the model using AlgebraicAgents.jl

## SIR Model in Agents.jl

To begin with, we define the Agents.jl model:

````@example agents
# SIR model for the spread of COVID-19
# taken from https://juliadynamics.github.io/Agents.jl/stable/examples/sir/
using AlgebraicAgents
using Agents, Random
using Agents.DataFrames, Agents.Graphs
using Distributions: Poisson, DiscreteNonParametric
using DrWatson: @dict
using Plots
@agent PoorSoul GraphAgent begin
days_infected::Int ## number of days since is infected
status::Symbol ## 1: S, 2: I, 3:R
end
````

Let's provide the constructors:

````@example agents
function model_initiation(;
Ns,
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)
rng = MersenneTwister(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 "
@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)
for c in 1:C
migration_rates[c, :] ./= migration_rates_sum[c]
end
properties = @dict(Ns,
Is,
β_und,
β_det,
β_det,
migration_rates,
infection_period,
infection_period,
reinfection_probability,
detection_time,
C,
death_rate)
space = GraphSpace(complete_digraph(C))
model = ABM(PoorSoul, space; properties, rng)
# Add initial individuals
for city in 1:C, n in 1:Ns[city]
ind = add_agent!(city, model, 0, :S) ## Susceptible
end
# add infected individuals
for city in 1:C
inds = ids_in_position(city, model)
for n in 1:Is[city]
agent = model[inds[n]]
agent.status = :I ## Infected
agent.days_infected = 1
end
end
return model
end
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)
Random.seed!(seed)
Ns = rand(50:5000, C)
β_und = rand(0.3:0.02:0.6, C)
β_det = β_und ./ 10
Random.seed!(seed)
migration_rates = zeros(C, C)
for c in 1:C
for c2 in 1:C
migration_rates[c, c2] = (Ns[c] + Ns[c2]) / Ns[c]
end
end
maxM = maximum(migration_rates)
migration_rates = (migration_rates .* max_travel_rate) ./ maxM
migration_rates[diagind(migration_rates)] .= 1.0
params = @dict(Ns,
β_und,
β_det,
migration_rates,
infection_period,
reinfection_probability,
detection_time,
death_rate,
Is)
return params
end
````

It remains to provide the SIR stepping functions:

````@example agents
function agent_step!(agent, model)
@get_model model
extract_agent(model, agent)
migrate!(agent, model)
transmit!(agent, model)
update!(agent, model)
recover_or_die!(agent, model)
end
function migrate!(agent, model)
pid = agent.pos
d = DiscreteNonParametric(1:(model.C), model.migration_rates[pid, :])
m = rand(model.rng, d)
if m ≠ pid
move_agent!(agent, m, model)
end
end
function transmit!(agent, model)
agent.status == :S && return
rate = if agent.days_infected < model.detection_time
model.β_und[agent.pos]
else
model.β_det[agent.pos]
end
d = Poisson(rate)
n = rand(model.rng, d)
n == 0 && return
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 = :I
n -= 1
n == 0 && return
end
end
end
update!(agent, model) = agent.status == :I && (agent.days_infected += 1)
function recover_or_die!(agent, model)
if agent.days_infected ≥ model.infection_period
if rand(model.rng) ≤ model.death_rate
@a kill_agent!(agent, model)
else
agent.status = :R
agent.days_infected = 0
end
end
end
````

That's it!

## Simulation Using AlgebraicAgents.jl

We instantiate a sample `ABM` model:

````@example agents
# create a sample agent based model
params = create_params(C = 8, max_travel_rate = 0.01)
abm = model_initiation(; params...)
````

Let's specify what data to collect:

````@example agents
infected(x) = count(i == :I for i in x)
recovered(x) = count(i == :R for i in x)
to_collect = [(:status, f) for f in (infected, recovered, length)]
````

We wrap the model as an agent:

````@example agents
m = ABMAgent("sir_model", abm; agent_step!, tspan=(0., 100.), adata=to_collect)
````

And we simulate the dynamics:

````@example agents
simulate(m)
````

````@example agents
draw(m)
````

Loading

0 comments on commit ffc5761

Please sign in to comment.