#ecology #virus-evolution

nightly bin+lib virolution

Agent-Based Simulation of Viral Evolution powered by a Sparse Haplotype Representation

1 unstable release

new 0.4.0 Jan 15, 2025

#32 in Simulation

40 downloads per month

MIT and AGPL-3.0-only

245KB
5.5K SLoC

virolution

Virolution - Agent-Based Simulation of Viral Evolution

Virolution is a simulation framework that is powered by a sparse genetic representation of viral genomes. It is designed to simulate the evolution of large viral populations with large genomes. The simulations core data structures are thread-safe and generic to allow for adaptation to a wide range of viral or other evolutionary systems.

How to use Virolution

There are two ways of using Virolution:

  • As a binary: You can use the binary to run simulations with a given configuration file. This will run a simulation that approximates the evolution of a viral population in discrete generations in potentially multiple compartments. The simulation can be configured to use different fitness landscapes, mutation rates, and population sizes.

  • As a library: You can use the core data structures to build your own simulations. This allows you to create custom simulations that are not possible with the binary. Using the libraries traits you can implement your own fitness landscapes, attribute providers, and host systems. You can also use non-discrete time steps and implement your own selection mechanisms.

Installation

To install the binary, you can use the following command:

cargo install virolution
cargo install virolution --features parallel

To use the library, you can add the following to your Cargo.toml:

[dependencies]
virolution = "0.5"

Basic Usage as a Binary

After installation, you can run the binary with a sequence file and a configuration file. A configuration file may look like this:

parameters:
 - mutation_rate: 1e-6
   recombination_rate: 0
   host_population_size: 10000
   infection_fraction: 1.0
   basic_reproductive_number: 100.0
   max_population: 10000
   dilution: 0.02
   substitution_matrix:
     - [0.0, 1.0, 1.0, 1.0]
     - [1.0, 0.0, 1.0, 1.0]
     - [1.0, 1.0, 0.0, 1.0]
     - [1.0, 1.0, 1.0, 0.0]
   fitness_model: !SingleHost
     distribution: !Exponential
       weights:
         beneficial: 0.29
         deleterious: 0.51
         lethal: 0.2
         neutral: 0.0
       lambda_beneficial: 0.03
       lambda_deleterious: 0.21
     utility: !Algebraic
       upper: 1.5
schedule:
 - generation: "{} % 1"
   event: transmission
   value: "[[0.9, 0.1], [0.1, 0.9]]"
 - generation: "{} % 200"
   event: sample
   value: 1000

This configuration file will run a simulation with a mutation rate of 1e-6, a host population size of 10000, and a basic reproductive number of 100.0. The substitution matrix is a 4x4 matrix with 0.0 on the diagonal and 1.0 elsewhere, meaning that all mutations are equally likely. The fitness model is a single host model with an exponential distribution of fitness effects, with a utility function that caps the fitness of the otherwise multiplicative fitness at 1.5.

In the schedule, we define two events: transmission and sample. The transmission event will occur every generation and will use a 2x2 matrix to determine the probability of transmission between two compartments. The sample event will occur every 200 generations and will sample 1000 individuals from the population. The {} in the generation field will be replaced with the current generation number and an event will be executed whenever the expression evaluates to 0.

There are more events that can be configured in the schedule.

Basic Usage as a Library

To use Virolution as a library, you can simply create your own binary in Rust and use the implementations provided by the library.

A wildtype haplotype is created from a sequence and an attribute definition. From it, mutants and recombinants can be derived:

use std::sync::Arc;

use virolution::core::*;
use virolution::core::fitness::init::*;
use virolution::core::fitness::utility::*;
use virolution::encoding::Nucleotide as Nt;

let sequence = vec![Nt::A; 4];
let mut attribute_definitions = AttributeSetDefinition::new();
attribute_definitions.register(Arc::new(
    FitnessProvider::from_model(
        "fitness",
        &sequence,
        &FitnessModel::new(FitnessDistribution::Neutral, UtilityFunction::Linear),
    )
    .expect("Failed to create fitness table"),
));

let wildtype = Haplotype::new(sequence, &attribute_definitions);
let mutant1 = wildtype.create_descendant(vec![2], vec![Nt::T]);
let mutant2 = wildtype.create_descendant(vec![1, 2], vec![Nt::G, Nt::C]);
let recombinant = Haplotype::create_recombinant(&mutant1, &mutant2, 0, 2);

assert_eq!(wildtype.get_sequence(), vec![Nt::A, Nt::A, Nt::A, Nt::A]);
assert_eq!(mutant1.get_sequence(), vec![Nt::A, Nt::A, Nt::T, Nt::A]);
assert_eq!(mutant2.get_sequence(), vec![Nt::A, Nt::G, Nt::C, Nt::A]);
assert_eq!(recombinant.get_sequence(), vec![Nt::A, Nt::A, Nt::C, Nt::A]);

Dependencies

~13–22MB
~303K SLoC