corries/lib.rs
1// Copyright (c) 2022-2023
2// Author: Tommy Breslein (github.com/tbreslein)
3// License: MIT
4
5#![warn(missing_docs)]
6
7//! Corries - CORrosive RIEman Solver
8//!
9//! A library/framework (honestly what's the difference nowadays anyways) to run 1D-hydrodynamics
10//! simulations solved with Riemann solvers.
11//! As such, corries is grid-based (in opposition to doing smooth particle hydrodynamics).
12//!
13//! Corries should be ready to use for simple shocktube-like simulations at this point.
14//!
15//! # Disclaimer about nomenclature
16//!
17//! Just a couple of notes about how things are named:
18//!
19//! * corries uses generalised coordinates, and names them `xi`, `eta`, and `Phi`
20//! * the coordinate systems in corries are always right-handed
21//! * corries may be specialised for 1D simulations, but the other dimensions are still important,
22//! even though they are only 1 cell wide
23//! * `xi` is the primary coordinate, the one that has more than one cell. That's why the
24//! coordinate vectors in [Mesh] are called xi_*, for example.
25//!
26//! # Dependencies
27//!
28//! Corries has one dependency that you need to use in your apps, which is `color_eyre`.
29//! Thus, in order to use corries, you need add to lines to the dependencies in your `Cargo.toml`:
30//!
31//! ```toml
32//! [dependencies]
33//! color-eyre = { version = "0.6", default-features = false }
34//! corries = { version = "0.4" }
35//! ```
36//!
37//! corries also has one default feature: `validation`.
38//! This feature performs run-time checks on the state of your simulation, like checking for finite
39//! values, positive mass densities and pressures, and the like.
40//!
41//! As useful as they are, they do slow down the simulation.
42//! Such you can disable it by turning off the the default-features by modifying the corries entry
43//! like this:
44//!
45//! ```toml
46//! corries = { versoin = "0.4", default-features = false }
47//! ```
48//!
49//! # Usage
50//!
51//! The main building blocks of preparing and running a simulation with corries are:
52//!
53//! * Choosing a couple of type parameters, namely for the traits: [Physics], [NumFlux], and
54//! [TimeSolver], as well as the [Mesh] size (also through a compile time constant)
55//! * Building a [CorriesConfig], which allows for fine-grained control over the simulation
56//! * Constructing and applying initial conditions to the necessary objects through the
57//! [CorriesConfig::init_corries]
58//! * Running the simulation with the [CorriesComponents]'s [Runner::run_corries] method
59//!
60//! You can see examples of this in the docs for [CorriesConfig::init_corries] and
61//! [Runner::run_corries], as well as when looking through the source code for the integration
62//! tests in the `tests/` folder.
63//! In those examples I usually use a couple of default constructors for configuration structs, but
64//! in `examples/template.rs` you can also see how a full-blown [CorriesConfig] can be set up.
65//!
66//! # Building a Sod test
67//!
68//! Let's walk through building a Sod test.
69//!
70//! The first thing we should do is setting some compile constants and type definitions.
71//! The type defs are not necessary, but I highly recommend adding them to make your code more
72//! flexible when you decide to switch out some of these type defs.
73//!
74//! ```
75//! use color_eyre::Result;
76//! use corries::prelude::*;
77//!
78//! // The number of grid cells in the Mesh
79//! const S: usize = 100;
80//!
81//! // Expands to:
82//! // type P = Euler1DAdiabatic<S>;
83//! // const E: usize = P::NUM_EQ; // which in turn equals 3
84//! set_Physics_and_E!(Euler1DAdiabatic);
85//!
86//! // The type for the numerical flux scheme
87//! type N = Kt<E, S>;
88//!
89//! // The type for the time discretisation scheme
90//! type T = RungeKuttaFehlberg<P, E, S>;
91//! ```
92//!
93//! To break this down, first we have a couple of `use` statements.
94//! corries features a prelude module that imports everything you need, and then you also need to
95//! use `color_eyre::Result`, because that is the return type for a lot of functions in `corries`.
96//!
97//! Then we need to set the size of your mesh, by definining `const S: usize`.
98//! In this case, we set the mesh to have 100 grid cells (including ghost cells).
99//!
100//! Next, we set up our [Physics] type.
101//! This determines the differential equations that are being solved in the simulation.
102//! You can check out the current options in the `Implementors` section of [Physics].
103//! This macro also sets up `const E: usize` to be the number of equations for the [Physics]
104//! implementor you chose.
105//!
106//! Then we set `N` to the [NumFlux] implementor we want, and `T` to the [TimeSolver] implementor.
107//! Again, check the docs for those traits to see the options.
108//!
109//! Next up, we need to set up our [CorriesConfig].
110//! This is probably the most complicated part, because there are so many options.
111//! In case this piece of the docs ever gets outdated, I would implore you to the [CorriesConfig]
112//! docs to check out the options you have, as well as the default constructors for that struct as
113//! well as some fields in it.
114//!
115//! Either way, a hand-rolled [CorriesConfig] for this type of simulation would look like this:
116//!
117//! ```
118//! # use color_eyre::Result;
119//! # use corries::prelude::*;
120//! # const S: usize = 100;
121//! # set_Physics_and_E!(Euler1DAdiabatic);
122//! # type N = Kt<E, S>;
123//! # type T = RungeKuttaFehlberg<P, E, S>;
124//! let config: CorriesConfig = CorriesConfig {
125//! // prints a welcome banner upon running [init_corries]
126//! print_banner: true,
127//!
128//! // Sets up a cartesian mesh, with the edges of the computational area (not counting ghost
129//! // cells) are at the coordinates `xi = 1.0` and `xi = 2.0`. Remember that `xi` is the name
130//! // of the primary coordinate in corries.
131//! mesh_config: MeshConfig {
132//! mode: MeshMode::Cartesian,
133//! xi_in: 1.0,
134//! xi_out: 2.0,
135//! },
136//!
137//! // Since we want to run an adiabatic simulaion, we need to set the adiabatic index.
138//! // For the Sod test, let's set it to `1.4`.
139//! // We can also set the type of units we want, but there is not much reason for it in
140//! // corries right now (yes, I should just remove it for now...). Just set it to
141//! // [UnitsMode::SI].
142//! physics_config: PhysicsConfig {
143//! adiabatic_index: 1.4,
144//! units_mode: UnitsMode::SI,
145//! },
146//!
147//! // Sets the boundary conditions for the west (inner) edge of the computational area.
148//! // [BoundaryMode::Custom] allows you to set individual boundary conditions for
149//! // every equation by putting pairs of the equation index the the custom boundary
150//! // mode in the embedded vector. Check out the documentation for the [Physics]
151//! // implementor you chose to see which indexes correspond to which equations, and check out
152//! // the documentation for [CustomBoundaryMode] to see what the available conditions are.
153//! //
154//! // Alternatively, this field could have been set to:
155//! // `boundary_condition_west: BoundaryMode::NoGradients`
156//! // This would set the `NoGradients` condition for all equations, without setting up this
157//! // vector. See `boundary_condition_east` to see how that would look like.
158//! boundary_condition_west: BoundaryMode::Custom(vec![
159//! (0, CustomBoundaryMode::NoGradients),
160//! (1, CustomBoundaryMode::NoGradients),
161//! (2, CustomBoundaryMode::NoGradients),
162//! ]),
163//!
164//! // Sets the boundary conditions for the east (outer) edge of the computational area.
165//! boundary_condition_east: BoundaryMode::NoGradients,
166//!
167//! // Sets up everything regarding numerics.
168//! numerics_config: NumericsConfig {
169//!
170//! // Sets up configuration for the numerical flux scheme.
171//! // [NumFluxConfig] is an enum that needs to correspond to the [NumFlux] implementor you
172//! // chose up top, otherwise the constructor for the [NumFlux] object will panic.
173//! //
174//! // `Kt` should be your go-to. It's fast and accurate, without running into Godunov's
175//! // order barrier since we only use linear limiting functions.
176//! numflux_config: NumFluxConfig::Kt {
177//! // When choosing `Kt`, you also need to set the limiter function here. Your go-to
178//! // should be the `VanLeer` limiter.
179//! limiter_mode: LimiterMode::VanLeer,
180//! },
181//!
182//! // Sets up the time integration scheme.
183//! // Currently, corries only supports Runge-Kutta-Fehlberg schemes, which are set here.
184//! time_integration_config: TimeIntegrationConfig::Rkf(RkfConfig {
185//! // The only important bit about this config is the exact scheme you want to use.
186//! // SSPRK5 and RKF4 are the go-to choices, though I would recommend the first
187//! // usually.
188//! rkf_mode: RKFMode::SSPRK5,
189//!
190//! // Whether or not to use automatic step control. Not really needed for this test,
191//! // so we set this to false.
192//! asc: false,
193//!
194//! // The relative tolerance when calculating the error between high and low order
195//! // solutions, used by the automatic step control.
196//! asc_relative_tolerance: 0.001,
197//!
198//! // The absolute tolerance when calculating the error between high and low order
199//! // solutions, used by the automatic step control.
200//! asc_absolute_tolerance: 0.1,
201//!
202//! // The timestep friction factor. This dampens new time steps widths calculated by
203//! // the automatic step control.
204//! asc_timestep_friction: 0.08,
205//! }),
206//!
207//! // How many full loop iterations corries is allowed to run before aborting the
208//! // simulation, if it has not ended due to other break conditions yet.
209//! iter_max: usize::MAX - 2,
210//!
211//! // The time coordinate at which the simulation starts.
212//! t0: 0.0,
213//!
214//! // The time coordinate at which the simulation ends. We set this to 0.25, so that the
215//! // the simulation stays contained within our spatial bounds.
216//! t_end: 0.25,
217//!
218//! // If the calculated time step width falls below this value, the simulation ends with
219//! // an error. This is to prevent simulations that would run on too long because
220//! // something is driving the time step width into the ground.
221//! dt_min: 1.0e-12,
222//!
223//! // The maximum value for the time step width. If the calculated time step width exceeds
224//! // this value, former will simply be capped to this.
225//! dt_max: f64::MAX,
226//!
227//! /// The CFL (Courant-Friedrichs-Lewy) condition parameter. Basically a safety factor to
228//! // dampen the time step widths calculated by the CFL condition.
229//! dt_cfl_param: 0.4,
230//! },
231//!
232//! // How many times should [Writer] write outputs during the simulation (not counting the
233//! // initial output).
234//! // These are evenly distributed throughout the simulation time, so for example, if you set
235//! // `numerics_config.t0 = 0.0` and `numerics_config.t_end = 10.0`, then setting
236//! // `output_counter_max = 10` would mean that corries writes output at
237//! // `t = [1.0, 2.0, ..., 10.0]` in addition to the extra output at `t = 0.0`.
238//! output_counter_max: 10,
239//!
240//! // Sets up how outputs are generated.
241//! // This is a vector, where every entry is a instance of [OutputConfig], and each of these
242//! // instances sets up one output stream. In this example, we set up to streams: one for
243//! // stdout (the first entry), and one for files (the second entry). You are free to mix and
244//! // match different output streams as you like, but this is probably a good go-to.
245//! writer_config: vec![
246//!
247//! // Sets up a stdout output stream, with TSV formatting (tab separated values) for
248//! // scalar values, i.e. values that do not differ for different parts of the
249//! // computational area.
250//! //
251//! // In most cases, setting this stream up like this is probably too verbose, so
252//! // OutputConfig provides constructor that you only need to pass the data_names field
253//! // to.
254//! // This same setup could have been achieved by replacing this initialiser with:
255//! // OutputConfig::default_stdout_with_names(
256//! // vec![DataName::Iter, DataName::T, DataName::Dt, DataName::DtKind]
257//! // ),
258//! //
259//! // You can also replicate this setup with the default_stout constructor:
260//! //
261//! // OutputConfig::default_stdout(),
262//! //
263//! // that sets up data_names exactly like this too.
264//! OutputConfig {
265//! // Sets up that this stream writes to stdout
266//! stream_mode: StreamMode::Stdout,
267//!
268//! // Formats the output vales such that the values are separated by tabs
269//! formatting_mode: FormattingMode::TSV,
270//!
271//! // Tells the stream to only print "global" data, i.e. values that are the same
272//! // everywhere in the computational area.
273//! string_conversion_mode: ToStringConversionMode::Scalar,
274//!
275//! // Ignored for stdout output; for file output this field sets the folder the
276//! // simulation data should be written to.
277//! folder_name: "".to_string(),
278//!
279//! // Ignored for stdout output; Whether to clear the contents of that folder during
280//! // initialisation.
281//! // THIS CAN LEAD TO LOSS OF DATA SO BE VERY CAREFUL WHAT FOLDER_NAME POINTS TO WHEN
282//! // SETTING THIS VALUE TO true!
283//! should_clear_out_folder: true,
284//!
285//! // Irrelevant for stdout output; for file output this field sets the base file name
286//! // for the simulation data.
287//! file_name: "".to_string(),
288//!
289//! // Floating point precision when printing floating point numbers
290//! precision: 3,
291//!
292//! // Ignored for scalar output; for vector output, this sets whether to include the
293//! // ghost cells in the output.
294//! should_print_ghostcells: false,
295//!
296//! // Whether to print the configuration metadata. For file based output, this
297//! // generates a <file_name>__metadata.json next to your simulation data, which is the
298//! // deserialised [CorriesConfig] for this simulation.
299//! should_print_metadata: true,
300//!
301//! // Defines which values should be printed to the output.
302//! // These values will be printed from left to right in the output. Check out the
303//! // docs for [DataName] for the options you have.
304//! data_names: vec![DataName::Iter, DataName::T, DataName::Dt, DataName::DtKind],
305//! },
306//!
307//! // Sets up a file output stream.
308//! //
309//! // There is also a handy constructor that sets up a default file output.
310//! // You could also define this exact same setup by constructing the OutputConfig like
311//! // this:
312//! // OutputConfig::default_file_with_names(
313//! // "results/sod",
314//! // "sod",
315//! // vec![DataName::T, DataName::XiCent, DataName::Prim(0), DataName::Prim(1), DataName::Prim(2)],
316//! // ),
317//! //
318//! // There is also OutputConfig::default_file() that sets the data_names field like this
319//! // automatically. You would call it like this:
320//! //
321//! // OutputConfig::default_file("results/sod", "sod", E),
322//! //
323//! // where E is the number of equations (we set this through the set_Physics_and_E macro
324//! // up top. This function would also replicate this exact setup.
325//! OutputConfig {
326//! // Configures this object to write to a file
327//! stream_mode: StreamMode::File,
328//!
329//! // Formats the data into csv files
330//! formatting_mode: FormattingMode::CSV,
331//!
332//! // Defines that the data is written as vectors. This allows the output to write
333//! // data like DataName::XiCent or DataName::Prim(n), which are vector-like values.
334//! string_conversion_mode: ToStringConversionMode::Vector,
335//!
336//! // Which folder to write the data to, relative to the cwd when calling the
337//! // executable.
338//! folder_name: "results/sod".to_string(),
339//!
340//! // Whether to clear out the folder defined in `folder_name` during initialisation.
341//! // THIS CAN LEAD TO LOSS OF DATA SO BE VERY CAREFUL WHAT FOLDER_NAME POINTS TO WHEN
342//! // SETTING THIS VALUE TO true!
343//! should_clear_out_folder: true,
344//!
345//! // The base file name for output files.
346//! // In this setup, your output files would be named:
347//! // `results/sod/sod_{00,01,02,..,10}.csv`
348//! file_name: "sod".to_string(),
349//!
350//! // Floating point precision for floating point numbers
351//! precision: 7,
352//!
353//! // Whether to include ghostcells when writing vector-like values.
354//! should_print_ghostcells: true,
355//!
356//! // Whether to write the metadata dump. In this setup, this file would be called:
357//! // `results/sod/sod__metadata.json`
358//! should_print_metadata: false,
359//!
360//! // Which values to include in this output, read from left to right.
361//! data_names: vec![
362//! DataName::T, DataName::XiCent, DataName::Prim(0), DataName::Prim(1), DataName::Prim(2)
363//! ],
364//! },
365//! ],
366//! };
367//! ```
368//!
369//! Now with our configuration done, we can initialise the objects we need.
370//! The [CorriesConfig::init_corries()] method does exactly what you need here, and all you need to
371//! pass it are the generic parameters we set up top, as well as a function for your initial
372//! conditions.
373//!
374//! That function has the signature (barring generic parameters):
375//!
376//! ```ignore
377//! fn(&mut State, &mut Solver, &Mesh) -> Result<()>
378//! ```
379//!
380//! As was already stated, the purpose of it is to apply your initial conditions to the [State]
381//! object.
382//!
383//! The Sod test for this set of equations is set up like this:
384//!
385//! * net zero velocities
386//! * a density and pressure jump in the horizontal centre of the shocktube:
387//! * To the left of that shock the mass density and pressure are set to 1.0
388//! * To the right of that shock the mass density is set to 0.125 and the pressure is set to 1.0
389//!
390//! When setting up initial conditions we generally only have to set up the primitive variables for
391//! the cell centric variable set.
392//! The [CorriesConfig::init_corries()] method handles applying the boundary conditions and making
393//! sure that [State::west] and [State::east] are set up too.
394//!
395//! ```no_run
396//! # use color_eyre::{eyre::Context, Result};
397//! # use corries::prelude::*;
398//! # const S: usize = 100;
399//! # set_Physics_and_E!(Euler1DAdiabatic);
400//! # type N = Kt<E, S>;
401//! # type T = RungeKuttaFehlberg<P, E, S>;
402//! # fn get_config<N: NumFlux<E, S> + 'static, const E: usize>(folder_name: &str, file_name: &str) -> CorriesConfig {
403//! # return CorriesConfig {
404//! # print_banner: false,
405//! # mesh_config: MeshConfig::default_riemann_test(),
406//! # physics_config: PhysicsConfig {
407//! # units_mode: UnitsMode::SI,
408//! # adiabatic_index: 1.4,
409//! # },
410//! # boundary_condition_west: BoundaryMode::NoGradients,
411//! # boundary_condition_east: BoundaryMode::NoGradients,
412//! # numerics_config: NumericsConfig::default_riemann_test::<N, E, S>(0.25),
413//! # output_counter_max: 1,
414//! # writer_config: vec![
415//! # OutputConfig::default_stdout(),
416//! # OutputConfig::default_file(folder_name, file_name, E),
417//! # ],
418//! # };
419//! # }
420//! # let config = get_config::<N,E>("results/sod", "sod");
421//! config.init_corries::<P, N, T, E, S>(|u, _, _| {
422//! // this function does need the [Solver] and [Mesh] objects, hence the two `_` in the
423//! // arguments.
424//! let breakpoint_index = (S as f64 * 0.5) as usize;
425//! for i in 0..breakpoint_index {
426//! // set mass density (rho) and pressure of the cell central values left of the shock to
427//! // 1.0
428//! u.cent.prim[[P::JRHO, i]] = 1.0;
429//! u.cent.prim[[P::JPRESSURE, i]] = 1.0;
430//!
431//! // set the xi velocity to 0.0
432//! u.cent.prim[[P::JXI, i]] = 0.0;
433//! }
434//! for i in breakpoint_index..S {
435//! // set the mass density to the right of the shock to 0.125
436//! u.cent.prim[[P::JRHO, i]] = 0.125;
437//!
438//! // set the pressure to the right of the shock to 0.1
439//! u.cent.prim[[P::JPRESSURE, i]] = 0.1;
440//!
441//! // set the xi velocity to 0.0
442//! u.cent.prim[[P::JXI, i]] = 0.0;
443//! }
444//! Ok(())
445//! }).unwrap();
446//! ```
447//!
448//! Obviously, you should be using the ? operator instead of unwrap, but then this doc test does
449//! not run correctly... ¯\_(ツ)_/¯
450//!
451//! The object returned here is a [CorriesComponents], which is simply a tuple around:
452//!
453//! * `u`: the [State] object that holds the state of the physical variables
454//! * `solver`: the [Solver] object that generates new solutions
455//! * `mesh`: the [Mesh] object that models the grid the simulation runs on
456//! * `writer`: the [Writer] object that handles writing output
457//!
458//! Almost done.
459//! Now we can call [Runner::run_corries] to run the simulation!
460//!
461//! ```no_run
462//! # use color_eyre::{eyre::Context, Result};
463//! # use corries::prelude::*;
464//! # const S: usize = 100;
465//! # set_Physics_and_E!(Euler1DAdiabatic);
466//! # type N = Kt<E, S>;
467//! # type T = RungeKuttaFehlberg<P, E, S>;
468//! # fn get_config<N: NumFlux<E, S> + 'static, const E: usize>(folder_name: &str, file_name: &str) -> CorriesConfig {
469//! # return CorriesConfig {
470//! # print_banner: false,
471//! # mesh_config: MeshConfig::default_riemann_test(),
472//! # physics_config: PhysicsConfig {
473//! # units_mode: UnitsMode::SI,
474//! # adiabatic_index: 1.4,
475//! # },
476//! # boundary_condition_west: BoundaryMode::NoGradients,
477//! # boundary_condition_east: BoundaryMode::NoGradients,
478//! # numerics_config: NumericsConfig::default_riemann_test::<N, E, S>(0.25),
479//! # output_counter_max: 1,
480//! # writer_config: vec![
481//! # OutputConfig::default_stdout(),
482//! # OutputConfig::default_file(folder_name, file_name, E),
483//! # ],
484//! # };
485//! # }
486//! # let config = get_config::<N,E>("results/sod", "sod");
487//! config.init_corries::<P, N, T, E, S>(|u, _, _| {
488//! // this function does need the [Solver] and [Mesh] objects, hence the two `_` in the
489//! // arguments.
490//! let breakpoint_index = (S as f64 * 0.5) as usize;
491//! for i in 0..breakpoint_index {
492//! // set mass density (rho) and pressure of the cell central values left of the shock to
493//! // 1.0
494//! u.cent.prim[[P::JRHO, i]] = 1.0;
495//! u.cent.prim[[P::JPRESSURE, i]] = 1.0;
496//!
497//! // set the xi velocity to 0.0
498//! u.cent.prim[[P::JXI, i]] = 0.0;
499//! }
500//! for i in breakpoint_index..S {
501//! // set the mass density to the right of the shock to 0.125
502//! u.cent.prim[[P::JRHO, i]] = 0.125;
503//!
504//! // set the pressure to the right of the shock to 0.1
505//! u.cent.prim[[P::JPRESSURE, i]] = 0.1;
506//!
507//! // set the xi velocity to 0.0
508//! u.cent.prim[[P::JXI, i]] = 0.0;
509//! }
510//! Ok(())
511//! }).unwrap().run_corries().unwrap();
512//! ```
513//!
514//! # Plans
515//!
516//! Currently, corries only supports cartesian meshes, though non-cartesian meshes are coming very
517//! soon.
518//!
519//! * Add cylindrical geometry + Source trait + GeometricSource + Sedov test
520//! * Add spherical geometry + new Sedov case
521//! * Add 2D adiabatic Euler and 2D isothermal Euler
522//! * Add adiabatic and isothermal Navier Stokes physics
523//! * Add logcylindrical geometry + Vortex test
524//! * Add gravitational source + Bondi test
525//! * Add viscosity source + Pringle test
526//! * investigate the possibility of putting boundary conditions into [State]
527//! * not really possible currently, because I cannot split the borrows between the boundary
528//! conditions and the variables without using Cell for interior mutability.
529//!
530//! Most importantly exports the module [prelude].
531
532mod boundaryconditions;
533pub mod components;
534pub mod config;
535mod errorhandling;
536#[macro_use]
537pub mod macros;
538pub mod directions;
539pub mod mesh;
540pub mod rhs;
541pub mod solver;
542pub mod state;
543pub mod time;
544pub mod units;
545pub mod writer;
546
547/// Exports everything you need to run a corries simulation. This includes the following modules
548///
549/// * [corries::components](crate::components)
550/// * [corries::config](crate::config)
551/// * [corries::directions](crate::directions)
552/// * [corries::mesh](crate::mesh)
553/// * [corries::rhs](crate::rhs)
554/// * [corries::solver](crate::solver)
555/// * [corries::state](crate::state)
556/// * [corries::time](crate::time)
557/// * [corries::units](crate::units)
558/// * [corries::writer](crate::writer)
559///
560/// as well as the [set_Physics_and_E] macro
561pub mod prelude {
562 pub use crate::components::*;
563 pub use crate::config::*;
564 pub use crate::directions::*;
565 pub use crate::mesh::*;
566 pub use crate::rhs::*;
567 pub use crate::set_Physics_and_E;
568 pub use crate::solver::*;
569 pub use crate::state::*;
570 pub use crate::time::*;
571 pub use crate::units::*;
572 pub use crate::writer::*;
573}
574pub use prelude::*;