fips_md/runtime/
mod.rs

1//! All things related to running FIPS simulations
2
3use std::collections::HashMap;
4
5use anyhow::{Result, anyhow};
6
7use crate::codegen::{CompiledRuntime};
8
9mod builder;
10mod domain;
11mod index;
12mod interaction;
13mod borrows;
14mod particle_data;
15mod types;
16
17pub use builder::*;
18pub use domain::*;
19pub use index::*;
20pub use interaction::*;
21pub use borrows::*;
22pub use particle_data::*;
23pub use types::*;
24
25use super::parser;
26
27pub struct Runtime {
28    /// Simulation domain
29    pub(crate) domain: Domain,
30    /// Current simulation time
31    current_time: f64,
32    /// Time step for simulation
33    time_step: f64,
34    /// RNG seeds
35    pub(crate) rng_seeds: Option<Vec<u64>>,
36    /// Store for particle data
37    pub(crate) particle_store: ParticleStore,
38    /// Index for particles
39    pub(crate) particle_index: ParticleIndex,
40    /// Possible simulations
41    pub(crate) simulation_index: SimulationIndex,
42    /// Global functions
43    pub(crate) function_index: FunctionIndex,
44    /// Defined interactions
45    pub(crate) interaction_index: InteractionIndex,
46    /// Enabled interactions
47    pub(crate) enabled_interactions: HashMap<InteractionID, InteractionDetails>,
48    /// Threads per particle type
49    pub(crate) threads_per_particle: HashMap<ParticleID, usize>,
50    /// Defined Constants
51    /// (these are required for the symbol table during code generation)
52    pub(crate) constants: HashMap<String, parser::SubstitutionValue>,
53}
54
55// Dimensionality of the particle system
56pub(crate) const BUILTIN_CONST_NDIM: &str = "NDIM";
57// Time step size
58pub(crate) const BUILTIN_CONST_DT: &str = "DT";
59// List of predefined constants
60pub(crate) const BUILTIN_CONSTANTS: &[&str] = &[
61    BUILTIN_CONST_NDIM,
62    BUILTIN_CONST_DT
63];
64
65impl Runtime {
66    pub fn new(unit: parser::Unit, domain: Domain, current_time: f64, time_step: f64) -> Result<Self> {
67        let ndim = domain.get_dim();
68        // Unpack parsed data from unit into runtime
69        // (TODO: Allow more than one unit -> include concept)
70        let particle_index = ParticleIndex::new(unit.particles)?;
71        let simulation_index = SimulationIndex::new(unit.simulations, &particle_index)?;
72        let interaction_index = InteractionIndex::new(unit.interactions, &particle_index)?;
73        let function_index = FunctionIndex::new(unit.extern_functiondecls)?;
74        let mut runtime = Self {
75            domain,
76            current_time, time_step,
77            rng_seeds: None,
78            particle_store: ParticleStore::new(),
79            particle_index, simulation_index, interaction_index, function_index,
80            enabled_interactions: HashMap::new(),
81            threads_per_particle: HashMap::new(),
82            constants: HashMap::new()
83        };
84        // Pre-defined constants
85        runtime.define_constant(BUILTIN_CONST_NDIM.into(), parser::SubstitutionValue::Usize(ndim), true)?;
86        // Return initialized runtime
87        Ok(runtime)
88    }
89
90    pub fn create_particles<'a>(&'a mut self, particle_name: &str, count: usize,
91        uniform_members: &UniformMembers, num_threads: usize) -> Result<ParticleBorrowMut<'a>>
92    {
93        // Find particle definition in index
94        let particle = match self.particle_index.get_particle_by_name(particle_name) {
95            None => return Err(anyhow!("Cannot find particle {}", particle_name)),
96            Some(particle) => particle
97        };
98        // Create particle
99        let particle_data = self.particle_store.create_particles(particle, count, &uniform_members)?;
100        self.threads_per_particle.insert(particle.0, num_threads);
101        // Return particle handle
102        let (particle_id, particle_definition) = particle;
103        Ok(ParticleBorrowMut::new(particle_id, particle_definition, particle_data))
104    }
105
106    /// Define a named constant of type i64
107    pub fn define_constant_i64<S: Into<String>>(&mut self, name: S, value: i64) -> Result<()> {
108        let name = name.into();
109        let value = parser::SubstitutionValue::I64(value);
110        self.define_constant(name, value, false)
111    }
112
113    /// Define a named constant of type f64
114    pub fn define_constant_f64<S: Into<String>>(&mut self, name: S, value: f64) -> Result<()> {
115        let name = name.into();
116        let value = parser::SubstitutionValue::F64(value);
117        self.define_constant(name, value, false)
118    }
119
120    /// Define a named constant of type usize
121    pub fn define_constant_usize<S: Into<String>>(&mut self, name: S, value: usize) -> Result<()> {
122        let name = name.into();
123        let value = parser::SubstitutionValue::Usize(value);
124        self.define_constant(name, value, false)
125    }
126
127    /// Internal function for defining constants
128    fn define_constant(&mut self, name: String, value: parser::SubstitutionValue, builtin: bool) -> Result<()> {
129        // Reject redefinition
130        if self.constants.contains_key(&name) {
131            return Err(anyhow!("Cannot redefine constant {}", name));
132        };
133        // Reject overriding of built-in constants
134        if !builtin && BUILTIN_CONSTANTS.iter().any(|builtin_name| *builtin_name == name) {
135            return Err(anyhow!("Cannot define constant {} due to built-in constant with the same name", name))
136        };
137        // Perform substitution
138        self.particle_index.substitute_constant(&name, &value)?;
139        self.interaction_index.substitute_constant(&name, &value)?;
140        self.function_index.substitute_constant(&name, &value)?;
141        // Remember substitution for debugging
142        self.constants.insert(name, value);
143        Ok(())
144    }
145
146    pub fn enable_interaction(&mut self, name: &str, details: InteractionDetails) -> Result<()> {
147        let (interaction,_) = self.interaction_index.get_interaction_by_name(name)
148            .ok_or(anyhow!("Cannot find interaction with name {}", name))?;
149        if self.enabled_interactions.contains_key(&interaction) {
150            return Err(anyhow!("Interaction with name {} is already enabled", name));
151        }
152        self.enabled_interactions.insert(interaction, details);
153        Ok(())
154    }
155
156    /// Get the current simulation time
157    pub fn get_time(&self) -> f64 {
158        self.current_time
159    }
160
161    /// Set the current simulation time
162    pub fn set_time(&mut self, time: f64) {
163        self.current_time = time
164    }
165
166    /// Get the current time step
167    pub fn get_time_step(&self) -> f64 {
168        self.time_step
169    }
170
171    /// Set the current time step
172    pub fn set_time_step(&mut self, time_step: f64) {
173        self.time_step = time_step
174    }
175
176    /// Seed thread RNGs
177    pub fn seed_rngs(&mut self, seeds: Vec<u64>) {
178        self.rng_seeds = Some(seeds);
179    }
180
181    /// Get an (estimate) of the memory currently allocated
182    /// This includes:
183    /// - Memory allocated for particle data
184    pub fn get_memory_usage(&self) -> usize {
185        let mut memory = 0;
186        memory += self.particle_store.get_memory_usage();
187        memory
188    }
189
190    /// Verify and compile runtime for a given simulation
191    pub fn compile(self, simulation_name: &str) -> Result<CompiledRuntime> {
192        // Gather all simulation ids
193        let (simulation, _) = self.simulation_index.get_simulation_by_name(simulation_name)
194            .ok_or(anyhow!("Cannot find simulation with name {}", simulation_name))?;
195        CompiledRuntime::new(self, simulation)
196    }
197
198    /// Borrow a particles data
199    pub fn borrow_particle_mut(&self, particle_name: &str) -> Result<ParticleBorrowMut> {
200        // Find particle definition in index
201        let (particle_id, particle_definition) = match self.particle_index.get_particle_by_name(particle_name) {
202            None => return Err(anyhow!("Cannot find particle {}", particle_name)),
203            Some(particle) => particle
204        };
205        let particle_data = self.particle_store.get_particle_mut(particle_id)
206            .ok_or(anyhow!("Particle type {} exists, but has not been initialized yet (call create_particles() first)", particle_name))?;
207        Ok(ParticleBorrowMut::new(particle_id, particle_definition, particle_data))
208    }
209}