NexusSim

NexusSim is a flexible and powerful Python framework for agent-based modeling of dynamic spatial processes. Designed for modularity and scientific reproducibility, it allows users to simulate complex emergent phenomena on customizable landscapes - from disease epidemics and invasive species to wildlife spread and information diffusion.

The framework is built on a decoupled, file-based architecture: the simulation engine produces a series of GeoTIFF raster files, which are then consumed by a comprehensive analysis toolbox to generate plots, animations, and statistics.

Key Features

    * Agent-Centric Simulation: Models complex behaviors by         focusing on individual agent states and interactions (nexussim.core).

    * Raster-Based I/O: Works seamlessley with standard geospatial data formats for defining landscapes and initial conditions.
    * Comprehensive Analysis Suite: A rich set of tools (nexussim.analysis) to generate epidemic curves, animations, persistence "hotspot" maps, and calculate the rate of spread.
    * Reproducibility First: The file-based output creates a permanent, reproducible record of every simulation run.
    * Scalable: By not holding the entire simulation history in memory, NexusSim can hanlde longer and larger simulations.

Installation

NexusSim is available on PyPI. Install it using pip:

`pip install nexussim`

To install the latest development version directly from GitHub:

`pip install git+https://github.com/mrmarcelino/nexussim.git`

Quickstart: From Simulation to Analysis

This example demonstrates the complete NexusSim workflow. It will:

    1. Create dummy input rasters for a landscape and initial infections.
    2. Run a 50-step simulation, saving the output to a results/ directory.
    3. Use the analysis module to generate an epidemic curve plot and an animated GIF from the results.

import os
import shutil
import numpy as np
import rasterio
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

# nexussim/
# |- __init__.py
# |- core.py
# |- analysis.py
from nexussim.core import DiseaseModel
import nexussim.analysis as nxs_analysis

# Helper function to create dummy input data
def create_dummy_raster(path, width, height, data, crs='EPSG:3857'):
  """
  Creates a simple GeoTIFF file for demonstration purposes.
  """
  profile ={
      'driver': 'GTiff',
      'dtype': 'float32',
      'width': width,
      'height': height,
      'count': 1,
      'crs': crs,
      'transform': rasterio.transform.from_origin(0, height, 1, 1),
      'nodata': -9999
  }
  with rasterio.open(path, 'w', **profile) as dst:
    dst.write(data.astype(profile['dtype']), 1)

# 1. Setup Environment and Input Data
print("--- Setting up simulation environment ---")
# Create directories for inputs, simulation outputs, and analysis outputs
os.makedirs('inputs', exist_ok=True)
os.makedirs('sim_results', exist_ok=True)
os.makedirs('analysis_outputs', exist_ok=True)

# Define file paths
cost_path = 'inputs/cost.tif'
initial_infection_path = 'inputs/initial_infection.tif'
width, height = 100, 100

# Create a uniform cost surface (all areas are equally passable)
cost_data = np.ones((height, width))
create_dummy_raster(cost_path, width, height, cost_data)

# Create an initial infection raster with a small cluster of infected cells
initial_infection_data = np.zeros((height, width))
initial_infection_data[45:55, 45:55] = 1
create_dummy_raster(initial_infection_path, width, height, initial_infection_data)
print(" > Dummy input rasters created.")

# 2. Run the Simulation
print("\n--- Running the NexusSim simulation ---")
model_params = {
    'cost_threshold': 1.0, # Agents can move anywhere
    'spread_probability': 0.6,
    'infection_duration': 5,
    'recovery_time_min': 10,
    'recovery_time_max': 20
}

# Initialize the model
model = DiseaseModel(cost_path, initial_infection_path, model_params)

# Run the simulation, which saves raster files to 'sim_results/'
model.run_simulation(time_steps=50, output_dir='sim_results')

# 3. Analyze the Results
print("\n--- Analyzing simulation results ---")
# First, get the list of generated files
tif_files = nxs_analysis.get_simulation_files('sim_results')

if tif_files:
  # Generate and save an epidemic curve plot
  curve_plot_path = 'analysis_outputs/epidemic_curve.png'
  nxs_analysis.generate_epidemic_curve(tif_files, curve_plot_path)

  # Create and save an animation
  animation_path = 'analysis_outputs/animation.gif'
  nxs_analysis.create_animation(tif_files, animation_path, duration=0.1)
  print(" > Analysis complete. Outputs are in 'analysis_outputs\'.")

  # 4. Display one of the generated plots
  print("\n--- Displaying generated epidemic curve ---")
  img = mpimg.imread(curve_plot_path)
  plt.figure(figsize=(10, 6))
  plt.imshow(img)
  plt.axis('off') # Hide axes as the image already has them
  plt.show()
else:
  print("Analysis skipped: No simulation files were found.")

The NexusSim Workflow

NexusSim is intentionally designed to separate the simulation from the analysis. This provides several key advantages for scientific modeling:

    1. Reproducibility: The output rasters serve as a permanent, shareable record of the simulation.
    2. Flexibility: You can run multiple analyses on a single simulation output without having to re-run the entire model.
    3. Extensibility: It's easy to write your own custom analysis functions that operate on the standardized GeoTIFF outputs.
The standard workflow looks like this: [Input Rasters] → nexussim.core.DiseaseModel → [Output Rasters] → nexussim.analysis → [Plots, GIFs, & Stats]

Core Components

    * nexussim.core: Contains the Agent and DiseaseModel classes. This is the heart of the simulation engine responsible for running the model and saving the state at each time step.
    * nexussim.analysis: A toolbox of standalone functions that take a list of raster file paths as input and produce high-level outputs like plots, statistics, or animations.

Contributing

Contributions are welcome! If you have ideas for new features, analysis tools, or improvements, please open an issue or submit a pull request.

License

This project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0) - see the LICENSE.md file for details.