diff --git a/12_steps_to_navier_stokes/Cargo.lock b/12_steps_to_navier_stokes/Cargo.lock
index bdb6ce4..b249eb2 100644
--- a/12_steps_to_navier_stokes/Cargo.lock
+++ b/12_steps_to_navier_stokes/Cargo.lock
@@ -2,6 +2,201 @@
# It is not intended for manual editing.
version = 4
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "poloto"
+version = "19.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "164dbd541c9832e92fa34452e9c2e98b515a548a3f8549fb2402fe1cd5e46b96"
+dependencies = [
+ "tagu",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
[[package]]
name = "steps"
version = "0.1.0"
+dependencies = [
+ "clap",
+ "poloto",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tagu"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddb6b06d20fba9ed21fca3d696ee1b6e870bca0bcf9fa2971f6ae2436de576a"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
diff --git a/12_steps_to_navier_stokes/Cargo.toml b/12_steps_to_navier_stokes/Cargo.toml
index 3515ccb..f5b0b3b 100644
--- a/12_steps_to_navier_stokes/Cargo.toml
+++ b/12_steps_to_navier_stokes/Cargo.toml
@@ -4,3 +4,5 @@ version = "0.1.0"
edition = "2024"
[dependencies]
+clap = { version = "4.6.0", features = ["derive"] }
+poloto = "19.1.2"
diff --git a/12_steps_to_navier_stokes/plots/step1.svg b/12_steps_to_navier_stokes/plots/step1.svg
new file mode 100644
index 0000000..39a3eba
--- /dev/null
+++ b/12_steps_to_navier_stokes/plots/step1.svg
@@ -0,0 +1,97 @@
+
diff --git a/12_steps_to_navier_stokes/src/lib.rs b/12_steps_to_navier_stokes/src/lib.rs
new file mode 100644
index 0000000..f7a122f
--- /dev/null
+++ b/12_steps_to_navier_stokes/src/lib.rs
@@ -0,0 +1,35 @@
+use crate::{
+ step_1::{Step1Config, one_dim_solver},
+ utils::plot_vectors,
+};
+
+mod step_1;
+mod utils;
+
+pub fn run_example_step1() {
+ let u0 = vec![
+ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0,
+ 2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ 1.0, 1.0, 1.0, 1.0, 1.0,
+ ];
+ let nx = 41.0;
+ let dx = 2.0 / (nx - 1.0);
+ let dt = 0.025;
+ let c = 1.0;
+
+ let config = Step1Config {
+ c,
+ dt,
+ dx,
+ u0,
+ timesteps: 25,
+ };
+ let uss = one_dim_solver(config);
+
+ plot_vectors(
+ &[uss[0].clone(), uss[12].clone(), uss[25].clone()],
+ Some(&["i=0", "i=12", "i=25"]),
+ "/Users/aselimov/repos/cfd_playground/12_steps_to_navier_stokes/plots/step1.svg",
+ )
+ .unwrap();
+}
diff --git a/12_steps_to_navier_stokes/src/main.rs b/12_steps_to_navier_stokes/src/main.rs
index e7a11a9..bbd8233 100644
--- a/12_steps_to_navier_stokes/src/main.rs
+++ b/12_steps_to_navier_stokes/src/main.rs
@@ -1,3 +1,18 @@
-fn main() {
- println!("Hello, world!");
+use clap::Parser;
+use steps::run_example_step1;
+
+#[derive(Parser, Debug)]
+#[command(version, about = "Simple runner for navier-stokes steps")]
+struct Args {
+ // Which step to run
+ #[arg(value_parser = clap::value_parser!(u8).range(1..=12))]
+ pub step_count: u8,
+}
+
+fn main() {
+ let args = Args::parse();
+ match args.step_count {
+ 1 => run_example_step1(),
+ _ => todo!(),
+ }
}
diff --git a/12_steps_to_navier_stokes/src/step_1/mod.rs b/12_steps_to_navier_stokes/src/step_1/mod.rs
new file mode 100644
index 0000000..565c343
--- /dev/null
+++ b/12_steps_to_navier_stokes/src/step_1/mod.rs
@@ -0,0 +1,75 @@
+pub struct Step1Config {
+ pub c: f64,
+ pub dt: f64,
+ pub dx: f64,
+ pub u0: Vec,
+ pub timesteps: usize,
+}
+
+/// Take a single timestep and calculate the updated u.
+/// NOTE: Our BC fixes u(x=0, t) = u(x=0, t=0) for simplicity.
+fn step(us: &[f64], dt: f64, dx: f64, c: f64) -> Vec {
+ us.iter()
+ .enumerate()
+ .map(|(i, u)| -> f64 {
+ if i > 0 {
+ u - (dt / dx) * c * (u - us[i - 1])
+ } else {
+ *u
+ }
+ })
+ .collect()
+}
+
+pub fn one_dim_solver(config: Step1Config) -> Vec> {
+ let mut uss: Vec> = vec![config.u0.clone()];
+
+ for iter in 1..config.timesteps + 1 {
+ uss.push(step(&uss[iter - 1], config.dt, config.dx, config.c));
+ println!("i = {iter}: {:?}", uss[iter])
+ }
+
+ uss
+}
+
+#[cfg(test)]
+mod test {
+ use crate::step_1::{Step1Config, one_dim_solver, step};
+
+ #[test]
+ fn test_step() {
+ let new_us = step(&[1.0, 2.0, 1.0], 0.5, 2.0, 1.0);
+
+ let correct_us = [1.0, 1.75, 1.25];
+ for (i, (sol, correct)) in new_us.iter().zip(correct_us).enumerate() {
+ println!("i={i} sol={sol} corect={correct}");
+ assert!(f64::abs(sol - correct) < 1e-12);
+ }
+ }
+
+ #[test]
+ pub fn test_tophat_sim() {
+ let baseline = [0.0; 5];
+ let top = [1.0; 5];
+ let config = Step1Config {
+ c: 3.0,
+ dt: 0.25,
+ dx: 10.0,
+ u0: [&baseline[..], &top[..], &baseline[..]].concat(),
+ timesteps: 1,
+ };
+
+ let uss = one_dim_solver(config);
+
+ let correct_solution = [
+ &baseline[..],
+ &[0.925, 1.0, 1.0, 1.0, 1.0, 0.075, 0.0, 0.0, 0.0, 0.0],
+ ]
+ .concat();
+
+ for (i, (sol, correct)) in uss[1].iter().zip(correct_solution).enumerate() {
+ println!("{i}, {sol}, {correct}");
+ assert!((sol - correct).abs() < 1e-12);
+ }
+ }
+}
diff --git a/12_steps_to_navier_stokes/src/utils.rs b/12_steps_to_navier_stokes/src/utils.rs
new file mode 100644
index 0000000..655e964
--- /dev/null
+++ b/12_steps_to_navier_stokes/src/utils.rs
@@ -0,0 +1,38 @@
+use poloto::prelude::*;
+use std::fs::File;
+use std::io::Write;
+
+pub fn plot_vectors(
+ data: &[Vec],
+ labels: Option<&[&str]>,
+ file_name: &str,
+) -> std::io::Result<()> {
+ let plots: Vec<_> = data
+ .iter()
+ .enumerate()
+ .map(|(i, v)| {
+ let label = labels
+ .and_then(|l| l.get(i).copied())
+ .map(|s| s.to_string())
+ .unwrap_or_else(|| format!("Vector {}", i + 1));
+ poloto::build::plot(label).line(v.iter().enumerate().map(|(x, &y)| [x as f64, y]))
+ })
+ .collect();
+
+ // 2. Pass the Vec directly — Vec implements PlotIterator.
+ let plotter =
+ poloto::frame_build()
+ .data(plots)
+ .build_and_label(("Data Comparison", "Index", "Value"));
+
+ // 3. Render and save
+ let content = plotter
+ .append_to(poloto::header().light_theme())
+ .render_string()
+ .unwrap();
+
+ let mut file = File::create(file_name)?;
+ write!(file, "{}", content)?;
+
+ Ok(())
+}
diff --git a/README.md b/README.md
index b53a0a7..031ab81 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,10 @@
Playground for me to mess around with CFD simulations.
First part of this will be implementing Lorena Barba's 12 steps to Navier-Stokes codes.
Also will be doing this in Rust because performance isn't critical here and I like Rust the best 🦀🦀🦀.
+
+## Notes on Rust Code
+
+Just fyi to anyone coming across this.
+This is a playground and not intended to be production grade rust.
+As a result, you may see some unwraps here and there (or other not safe/not production Rust).
+I don't suggest copying this code exactly if you are writing your own production grade solvers.