Getting Started¶
This guide walks through the core workflows: generating networks with controlled clustering and running epidemic simulations on them.
Generating networks¶
Erdos-Renyi random graph¶
The simplest model — each edge exists independently with probability \(p\).
import numpy as np
from craeft import random_graph
rng = np.random.default_rng(42)
adjacency = random_graph(n=1000, p=0.01, rng=rng)
Configuration model (unclustered)¶
Prescribe an exact degree sequence. The result has near-zero clustering.
from craeft import configuration_model
degrees = np.full(1000, 5) # all nodes degree 5
adjacency = configuration_model(degrees, rng=rng)
Clustered configuration model¶
Add clustering by setting phi > 0. The CCM allocates a fraction of each
node's stubs to motif participation (triangles, K4 cliques) before pairing
the remainder via the standard configuration model.
Using generator objects¶
For experiments that generate many networks with the same parameters,
use the generator dataclasses. These are frozen, picklable, and compose
cleanly with the Experiment orchestrator.
from craeft import (
PoissonNetworkGenerator,
BigVRewiringGenerator,
MotifDecompositionGenerator,
)
# Poisson degree distribution with clustering
gen = PoissonNetworkGenerator(n=1000, mean_degree=5, max_degree=20, phi=0.2)
# Big-V rewiring on top of an unclustered base
base = PoissonNetworkGenerator(n=1000, mean_degree=5, max_degree=20)
gen = BigVRewiringGenerator(base=base, target_clustering=0.2)
# Motif decomposition (start from cliques, tear down to target)
gen = MotifDecompositionGenerator(
num_nodes=1000, clique_size=6, target_clustering=0.2,
)
adjacency = gen.generate(rng)
Measuring network structure¶
from craeft import global_clustering_coefficient, is_connected
from craeft.networks.metrics import (
count_triangles,
local_clustering,
triangles_per_node,
)
print(f"Connected: {is_connected(adjacency)}")
print(f"Triangles: {count_triangles(adjacency)}")
print(f"Global clustering: {global_clustering_coefficient(adjacency):.4f}")
Running SIR epidemics¶
Single realisation¶
Use the low-level run_once function for a single Gillespie trajectory.
from craeft.point_processes import run_once, ConvergenceConfig
from craeft.point_processes.epidemics import SIRConfig, SIRProcessFactory
sir = SIRConfig(tau=1.0, gamma=1.0, initial_infected=5)
convergence = ConvergenceConfig(t_end=15.0)
factory = SIRProcessFactory(config=sir, adjacency=adjacency, convergence_config=convergence)
trajectory, final_size, accepted = run_once(factory, t_end=15.0, rng=rng)
print(f"Final size: {final_size}")
Ensemble with convergence¶
The SIRSimulator runs multiple realisations, monitors convergence of
the mean final epidemic size via the relative standard error, and
optionally filters sub-critical outbreaks.
from craeft.point_processes.epidemics import SIRSimulator
convergence = ConvergenceConfig(
t_end=15.0,
convergence_threshold=0.05,
min_realizations=30,
max_realizations=500,
)
simulator = SIRSimulator(config=sir, convergence=convergence)
result = simulator.run(adjacency, rng)
print(f"Converged: {result.convergence.converged}")
print(f"Realisations: {result.convergence.n_realizations}")
print(f"Mean final size: {result.scalar_output_mean:.1f} ± {result.scalar_output_std:.1f}")
Running full experiments¶
The Experiment class composes a generator and simulator, running
multiple network realisations with optional parallelism.
from craeft import Experiment, PoissonNetworkGenerator
from craeft.point_processes.epidemics import SIRSimulator, SIRConfig
from craeft.point_processes import ConvergenceConfig
generator = PoissonNetworkGenerator(n=500, mean_degree=5, max_degree=20, phi=0.2)
simulator = SIRSimulator(
config=SIRConfig(tau=1.0, gamma=1.0, initial_infected=5),
convergence=ConvergenceConfig(t_end=15.0, max_realizations=200),
)
experiment = Experiment(generator=generator, simulator=simulator, n_networks=10)
results = experiment.run(rng=rng, n_workers=1)
for i, ensemble in enumerate(results):
print(f"Network {i}: final size = {ensemble.scalar_output_mean:.1f}")
Plotting¶
from craeft.utils.plotting import plot_sir, plot_prevalence_comparison
fig = plot_sir(
t=result.t,
s_mean=result.means["susceptible"],
i_mean=result.means["infected"],
r_mean=result.means["recovered"],
s_std=result.stds["susceptible"],
i_std=result.stds["infected"],
r_std=result.stds["recovered"],
)
fig.savefig("sir_dynamics.pdf")