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::*;