We will first dig into the Plotters crate and plot simple diagrams.
Then we will successive build up a configuration, simulation and visualization for the model as discussed with Jonathan in the Video.
The constants used by to configure the two case-studies FN and KMIR can be found in v2_hints.rs, even when you checked out the task commit.
The model uses many coefficients and writing the model equations is a bit of grinding work. Therefore, this episodes more sophistcated model is implemented in v2_hints.rs if you're checked out the hints commit.
General:
- Simulation of a more sophisticated model of a multi-bed Haber-Bosch Reactor
- Fugacity coefficients describing non-idealist gas
- Dependent on Temperature and Pressure
- Use two different Catalysts.
- Sequential simulation of a multiple bed reactor
- Modularization over several files
- Concepts behind Iterators and Closures
Rust:
- (easy) Nested Data Structures
- (easy) The builder pattern
- (easy) Using Iterators
- (advanced) Build our own Iterator type
- (advanced) Coarse Words on Lifetimes in Rust
We will divide our work in four parts:
- Implement Plotting Functionality
- Haber-Bosch: Configuration
- Haber-Bosch: Simulation
- Haber-Bosch: Visualization
- (optional - git) Checkout the hints or task commit and create a custom git branch for the upcoming work
- Adapt the
Cargo.toml
file- Add dependency to plotters in version
0.3.5
- Add the following binaries: (keywords for Cargo.toml are
default-run
and[[bin]]
)hb_ode_test
withmain.rs
as source.hb_plot_test
withv2_main_plot.rs
as source.hb_seq_simulation
withv2_main.rs
as source.
- Add dependency to plotters in version
- Draw an empty png to the file system.
- Add a chart to the plot:
- set a caption, label area sizes of 40 and a margin of 12.
- use a range of -3 .. 3 for x and -10 .. 10 for y.
- create a mesh with a y description, e.g.
magic numbers
.
- Draw the function
y = x^3
as aLineSeries
. - Refactoring: Extract the chart generation functionality in a helper function
prepare_chart
. - Implement an additional function
prepare_chart_dual
.
Reminder: Constants for the case-studies KMIR and FN can be found in v2_hint.rs (even if you checked out the task commit).
- Start modularzing your project:
- Add the files
configuration.rs
,simulation.rs
andvisualization.rs
. - Move the functions
prepare_chart
andprepare_chart_dual
intovisualization.rs
.
- Add the files
- In
simulation.rs
add aHaberBoschModel
structure that contains all the important constants, later we will implement theSystem
trait on it. - Introduce a nested data-structure that is capable of representing a Haber-Bosch configuration in
configuration.rs
, think about:- What data needs to be store and how can we support any number of reactor beds?
- Use this structure to store results of ODE-solver (
x_out
andy_out
), what is the dimensionality ofy_out
? - What data is needed by the ODE-solver and how can we provide it?
- Write an accessor function that provides a data structure, for the n'th bed, that can can be passed to the ODE-Solver.
- In
v2_main.rs
implement a loop over the to catalystscatalysts=[KMIR, FN]
, for each:- Generate the nested data structure, use the constants you find for the beds in hint.rs (optional make use of the Builder pattern)
- Access the solver parameters for the first bed
- output them
- Implement the
system
method of theSystem
trait forHaberBoschModel
- Reminder This episodes more sophistcated model is implemented in v2_hints.rs if you're checked out the hints commit. - Implement the
solout
method of theSystem
trait forHaberBoschModel
- Use it to stop the simulation as soon as there is nearly no change in Ammonia. - Implement a function
sequential_simulation
with a mutable reference to your nested data structure as a parameter and no return type, the function shall:- For each reactor bed:
- Output the starting point.
- Simulate it with the
Dopri5
solver (IMPORTANT, other solver do not yet supportsolout
but the fix will be in the next version). - Store the results in your nested data structure.
- For each reactor bed:
- Add helper methods to the nested data structure:
is_simulation_done
checks if there are as many results as reactor beds.print_summary
outputs the length of each reactor bed, the complete length and the final yield of Ammonia.
- Overwork the main loop: Call
sequential_simulation
and output the summary.
Example Output of print_summary
to check if your solution is working as expected:
Summary based on Catalyst KMIR
Reactor Length: 0.787 + 1.500 = 2.287
Final Yield: 0.22439126651044983
Summary based on Catalyst FN
Reactor Length: 0.313 + 0.400 = 0.712
Final Yield: 0.23430821247073505
We divide this in two tasks
- Draw a temparature over yield diagram
- Draw a concentration balance diagram.
- As perparation implemment the methods:
get_ammonia_range
that returns aRange<f32>
the range for plotting on the x-axisget_temp_range
that returns aRange<f32>
for for the y-axis when plotting temperature
- Add a function
draw_temperature_over_yield
with an&str
(string slice) as argument for the filename, e.g.diagram.png
, and a reference to the nested data structure.- Create a chart with axis and labels by using the
prepare_chart
- Store the chart with the corresponding file name
- Create a chart with axis and labels by using the
Now we will switch from using Iterators to implementing our own Iterator.
Think about the following requirements:
- We want an Iterator that can be consumed by plotters, over what Item Type will we iterate?
- We want be capable select temperatures and balances as y. The balances may either be concentrations or partial fractions.
- What variables allow us to select the form of y and what internal indicies are needed to iterate over the data in the nested data structure?
- Add a data structure
MyIterator<'a>
it needs a reference to the nested data structure with lifetime'a
- Remember the requirements above add the config and internal variables to
MyIterator
- Implement the
Iterator
trait forMyIterator
:impl<'a> Iterator for MyIterator<'a> { ... }
.- Select the Item type
- Implement
next
which returns an Option which is an Enum.
- Add the method
my_iter
to the nested data structure, forward the configuratoin arguments to a newMyIterator
object and initialize its indicies to point at the first data point in the nested data structure.
Use the my_iter()
method and prepare_chart_dual
to also render a concentration balance diagram which shows the temperature on the secondary axis.
KMIR Temperature over Yield Diagram
KMIR Concentration Balance Diagram
FN Temperature over Yield Diagram
FN Concentration Balance Diagram
For this Episode we recommend to learn a lot about Iterators - There are many methods to adapt or consume them, see the Iterator Reference
As some of your are starting your journey with Rust and this course cannot give an in-depth introduction but focuses and solving problems with Rust and on this way just provides enough detail, we want to make you aware of those very good starter materials:
- The Rust Book - Gives an introduction suitable for people who have experiences in other coding languages.
- Rust By Examlpe - A collection of code examples with short explanations similiar to the
hints.rs
files you find in this course. - Rustlings - A project containing small Rust exercises and let you solve them in an interactive way. It follows the structure of the rust book.
- The Cargo book - although we won't dig deep into it, we will play around with multiple binaries and we have a workspace structure that may become in handy in the future.
Here we will appy, enum for Catalyst represenation. We use only a simple form but Enums
and match
are very nice concepts in Rust and you read about them in Chapter 6
We use multipe files to modularize our project that is explained in Chapter 7.2 - Information about the format in Cargo.toml can be found in The Manifest Format
You can revisit Chapter 10 and learn about use generic data types and explicit lifetimes, we will use both with plotters.
And most importantly Chapter 13.2 explains Iterators and Closures in more detail.