From 745ed4bc175359216d22c911ba29ab7101327f1f Mon Sep 17 00:00:00 2001 From: Broky64 Date: Wed, 5 Nov 2025 15:33:18 +0100 Subject: [PATCH 1/3] Fix typo in .gitignore for output directory path --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2787c44..c014479 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ .idea/ # Outputs -/results/* +/result/* !.gitkeep # MacOS From 3ebb1e9a15f447c8f54b690d0a128bbf9ee824a0 Mon Sep 17 00:00:00 2001 From: Broky64 Date: Wed, 5 Nov 2025 15:33:29 +0100 Subject: [PATCH 2/3] Implement Richardson scheme for 1D heat equation and update method interface --- include/method.hpp | 3 +- include/methods/richardson.hpp | 60 ++++++++++++++++++ src/main.cpp | 107 +++++++++++++++++++++------------ src/method.cpp | 35 ++++++++--- src/methods/richardson.cpp | 25 ++++++++ src/solver.cpp | 58 +++++++----------- 6 files changed, 204 insertions(+), 84 deletions(-) create mode 100644 include/methods/richardson.hpp create mode 100644 src/methods/richardson.cpp diff --git a/include/method.hpp b/include/method.hpp index 5488170..a8d2250 100644 --- a/include/method.hpp +++ b/include/method.hpp @@ -13,7 +13,8 @@ * @brief Supported numerical schemes. */ enum class SchemeKind { - DuFortFrankel + DuFortFrankel, + Richardson // Richardson, Laasonen, CrankNicolson (to be added later) }; diff --git a/include/methods/richardson.hpp b/include/methods/richardson.hpp new file mode 100644 index 0000000..80f23da --- /dev/null +++ b/include/methods/richardson.hpp @@ -0,0 +1,60 @@ +#pragma once +/** + * @file richardson.hpp + * @brief Richardson explicit (central time - central space) scheme for the 1D heat equation. + */ + +#include +#include +#include "../grid.hpp" +#include "../method.hpp" + +/** + * @class Richardson + * @brief Implementation of the explicit Richardson scheme (CTCS). + * + * The scheme reads: + * \f[ + * T_i^{n+1} = T_i^{n-1} + 2 r \left( T_{i+1}^n - 2 T_i^n + T_{i-1}^n \right), + * \quad r = \frac{D \, \Delta t}{\Delta x^2} + * \f] + * + * It is a three-level scheme, therefore it requires both \f$T^{n-1}\f$ and \f$T^n\f$ + * to produce \f$T^{n+1}\f$. Boundary conditions are not enforced here, but left + * to the solver. + */ +class Richardson : public Method { +public: + /** + * @brief Indicate that this is a three-level scheme. + * @return true always, since Richardson needs \f$T^{n-1}\f$. + */ + bool uses_previous_step() const noexcept override { return true; } + + /** + * @brief Get human-readable name of the scheme. + * @return "Richardson" + */ + std::string name() const override { return "Richardson"; } + + /** + * @brief Advance one time step using the Richardson explicit scheme. + * + * This computes \f$T^{n+1}\f$ from \f$T^{n}\f$ and \f$T^{n-1}\f$. + * Interior nodes \f$i = 1, \dots, N-2\f$ are updated, while boundary + * nodes are left untouched (the solver will impose Dirichlet BCs). + * + * @param g Grid metadata (spacing and number of nodes) + * @param D Diffusivity [cm^2/h] + * @param dt Time step [h] + * @param Tprev Temperature field at previous time layer \f$T^{n-1}\f$ + * @param Tcurr Temperature field at current time layer \f$T^{n}\f$ + * @param Tnext Output temperature field to be filled with \f$T^{n+1}\f$ + */ + void step(const Grid& g, + double D, + double dt, + const std::vector& Tprev, + const std::vector& Tcurr, + std::vector& Tnext) override; +}; diff --git a/src/main.cpp b/src/main.cpp index 08d4330..b45a499 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,60 +1,87 @@ /** * @file main.cpp - * @brief Simple test program for the Grid and user input utilities. - * - * This program: - * 1. Asks the user for Δx (spatial step) - * 2. Builds a uniform grid from 0 to L = 31 cm - * 3. Validates its consistency (assertions in Debug mode) - * 4. Prints grid information and first/last nodes + * @brief Test driver for the 1D heat conduction solver (Richardson scheme), + * saving temperature profiles as CSV files. */ -#include "user_input.h" #include "types.hpp" #include "grid.hpp" +#include "solver.hpp" +#include "method.hpp" +#include "user_input.h" +#include "io.hpp" // <-- pour save_profile_csv() + #include #include +#include +#include + +/** + * @brief Output callback: save temperature profiles to CSV at each dump time. + * + * The output folder is automatically created under `result/richardson/`. + * Example files: `result/richardson/t_0.00.csv`, `t_0.10.csv`, etc. + * + * @param t Current time [h] + * @param g Grid object (for x-locations) + * @param T Temperature field [°C] + */ +void save_to_csv(double t, const Grid& g, const std::vector& T) +{ + // Build file name like "t_0.10.csv" + std::ostringstream name; + name << "richardson/t_" << std::fixed << std::setprecision(2) << t << ".csv"; + + // Save profile to CSV using helper from io.cpp + save_profile_csv(name.str(), g.x, T); -int main() { + std::cout << "Saved: result/" << name.str() << "\n"; +} + +/** + * @brief Entry point for testing the Richardson explicit scheme. + */ +int main() +{ try { - // --- Physical constants (fixed from assignment) --- + std::cout << "=== 1D Heat Equation Solver (Richardson test) ===\n"; + + // --- Physical parameters (assignment constants) --- PhysParams phys; - std::cout << "=== Grid initialization test ===\n"; - std::cout << "Domain length L = " << phys.L_cm << " cm\n"; - - // --- Ask user for dx --- - double dx = askForDouble( - "Enter spatial step Δx [cm] (suggested 0.05): ", - [](double v){ return v > 0.0 && v <= 1.0; }, - "Δx must be positive and reasonably small (<= 1.0 cm)." - ); - - // --- Create and validate grid --- - Grid g(phys.L_cm, dx); + phys.L_cm = 31.0; ///< Wall thickness [cm] + phys.Tin = 38.0; ///< Initial temperature [°C] + phys.Tsur = 149.0; ///< Boundary temperature [°C] + phys.D_cm2h = 93.0; ///< Diffusivity [cm²/h] + + // --- Numerical parameters --- + NumParams num; + num.dx = askForDouble("Enter Δx [cm] (suggested 0.05): ", + [](double v){ return v > 0.0 && v <= 1.0; }, + "Δx must be positive and <= 1.0 cm."); + num.dt = 0.01; ///< Time step [h] + num.tEnd = 0.5; ///< Simulation duration [h] + num.outEvery = 0.1; ///< Output interval [h] + + // --- Build grid --- + Grid g(phys.L_cm, num.dx); validate_grid(g); - // --- Output basic info --- - std::cout << std::fixed << std::setprecision(4); - std::cout << "\nGrid successfully created ✅\n"; - std::cout << " Number of nodes Nx : " << g.Nx << "\n"; - std::cout << " Spatial step dx : " << g.dx << " cm\n"; - std::cout << " Domain length L : " << g.L << " cm\n"; - std::cout << " First node x[0] : " << g.x.front() << " cm\n"; - std::cout << " Last node x[N-1] : " << g.x.back() << " cm\n\n"; - - // Optional: show first and last few nodes - std::cout << "First 5 nodes: "; - for (std::size_t i = 0; i < std::min(5, g.Nx); ++i) - std::cout << g.x[i] << " "; - std::cout << "\nLast 5 nodes: "; - for (std::size_t i = g.Nx - std::min(5, g.Nx); i < g.Nx; ++i) - std::cout << g.x[i] << " "; - std::cout << "\n"; + std::cout << "\nGrid created ✅ Nx = " << g.Nx + << ", dx = " << g.dx << " cm\n"; + + // --- Create solver with Richardson scheme --- + HeatSolver solver(phys, num, SchemeKind::Richardson); + std::cout << "Running Richardson scheme...\n"; + + // --- Run simulation and save results --- + solver.run(save_to_csv); + std::cout << "\nSimulation completed successfully ✅\n"; + std::cout << "Results available under ./result/richardson/\n"; return 0; } catch (const std::exception& e) { - std::cerr << "Error: " << e.what() << "\n"; + std::cerr << "Fatal error: " << e.what() << "\n"; return 1; } } diff --git a/src/method.cpp b/src/method.cpp index 91a6ae4..fff454e 100644 --- a/src/method.cpp +++ b/src/method.cpp @@ -1,16 +1,35 @@ +/** + * @file method.cpp + * @brief Factory for numerical schemes. + */ + #include "method.hpp" +#include "methods/richardson.hpp" #include -#include +/** + * @brief Build a concrete numerical method from the requested scheme kind. + * + * In this branch we only compile / enable the Richardson method. Other + * schemes (e.g. DuFort-Frankel, Laasonen, Crank-Nicolson) will be added + * in their own branches. + * + * @param scheme Identifier of the requested scheme. + * @return std::unique_ptr Owning pointer to the created scheme. + * @throws std::invalid_argument if the scheme is not available in this build. + */ std::unique_ptr make_method(SchemeKind scheme) { - // For now, no concrete method is available in this branch. - // The switch is kept so that the code remains aligned with the enum in the header. switch (scheme) { - case SchemeKind::DuFortFrankel: - throw std::invalid_argument( - "make_method: DuFort-Frankel is not compiled in this branch"); - } + case SchemeKind::Richardson: + return std::make_unique(); - throw std::invalid_argument("make_method: unsupported scheme"); + case SchemeKind::DuFortFrankel: + // pas dispo dans cette branche + throw std::invalid_argument( + "make_method: DuFort-Frankel is not compiled in this branch"); + + default: + throw std::invalid_argument("make_method: unknown scheme"); + } } diff --git a/src/methods/richardson.cpp b/src/methods/richardson.cpp new file mode 100644 index 0000000..a79ab77 --- /dev/null +++ b/src/methods/richardson.cpp @@ -0,0 +1,25 @@ +/** + * @file richardson.cpp + * @brief Richardson explicit scheme implementation. + */ + +#include "methods/richardson.hpp" +#include // for std::size_t + +void Richardson::step(const Grid& g, + double D, + double dt, + const std::vector& Tprev, + const std::vector& Tcurr, + std::vector& Tnext) +{ + const double r = D * dt / (g.dx * g.dx); + const std::size_t N = g.Nx; + + // Update interior nodes only; boundaries are imposed by the solver. + for (std::size_t i = 1; i + 1 < N; ++i) { + Tnext[i] = Tprev[i] + + 2.0 * r * ( Tcurr[i + 1] - 2.0 * Tcurr[i] + Tcurr[i - 1] ); + } + // Tnext[0] and Tnext[N-1] left as-is +} diff --git a/src/solver.cpp b/src/solver.cpp index f0e5f86..27da29f 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -1,81 +1,71 @@ +/** + * @file solver.cpp + * @brief Time-marching driver for the 1D heat equation. + */ + #include "solver.hpp" -#include +#include // std::fill HeatSolver::HeatSolver(const PhysParams& phys, const NumParams& num, SchemeKind scheme) : phys_(phys) , num_(num) - , grid_(phys.L_cm, num.dx) // Grid construction + , grid_(phys.L_cm, num.dx) { - // Create the computation method (factory according to your code) + // on récupère la bonne méthode via la factory (définie dans method.cpp) method_ = make_method(scheme); uses_prev_step_ = method_->uses_previous_step(); - // Allocate vectors + // allocation T_.resize(grid_.Nx); next_.resize(grid_.Nx); if (uses_prev_step_) { Tprev_.resize(grid_.Nx); } - // Initialize the field + // init init_field(); - - // Bootstrapping for three-level methods (such as DuFort–Frankel) bootstrap_if_needed(); } void HeatSolver::apply_dirichlet(std::vector& T) const { - // Impose surface temperature at both ends T.front() = phys_.Tsur; T.back() = phys_.Tsur; } void HeatSolver::init_field() { - // Uniform initial field at Tin std::fill(T_.begin(), T_.end(), phys_.Tin); - - // Boundary conditions apply_dirichlet(T_); } void HeatSolver::bootstrap_if_needed() { - if (!uses_prev_step_) { + if (!uses_prev_step_) return; - } - // Here, T_ = T^0 - - // 1) Store T^0 in Tprev_ + // T_ = T^0 Tprev_ = T_; - // 2) Compute T^1 using a small FTCS step (1D diffusion) const double dx = grid_.dx; const double r = phys_.D_cm2h * num_.dt / (dx * dx); const std::size_t N = grid_.Nx; - // Start from T^0 next_ = T_; - // FTCS scheme (conditional stability: r <= 0.5) + // petit FTCS pour construire T^1 if (r <= 0.5) { for (std::size_t i = 1; i + 1 < N; ++i) { - next_[i] = T_[i] + r * (T_[i + 1] - 2.0 * T_[i] + T_[i - 1]); + next_[i] = T_[i] + r * (T_[i+1] - 2.0 * T_[i] + T_[i-1]); } } else { - // If dt is too large, keep T^1 = T^0 to avoid oscillations next_ = T_; } - // Boundary conditions apply_dirichlet(next_); - - // 3) Update T_ to contain T^1 - T_ = next_; + T_ = next_; // T^1 } void HeatSolver::run( @@ -85,35 +75,33 @@ void HeatSolver::run( const double dt = num_.dt; const double tEnd = num_.tEnd; const double outEvery = num_.outEvery; + double nextDump = 0.0; - double nextDump = 0.0; - - // Initial dump + // sortie initiale onDump(t, grid_, T_); - // Main time loop while (t < tEnd - 1e-12) { if (uses_prev_step_) { - // Three-level method (e.g., DuFort–Frankel) + // schémas à 3 niveaux (Richardson) method_->step(grid_, phys_.D_cm2h, dt, Tprev_, T_, next_); } else { - // Two-level method (e.g., FTCS, BTCS…) + // schémas à 2 niveaux (pas utilisés dans cette branche) method_->step(grid_, phys_.D_cm2h, dt, T_, T_, next_); } - // Apply boundary conditions + // BC apply_dirichlet(next_); - // Shift time states + // shift if (uses_prev_step_) { Tprev_ = T_; } T_ = next_; - // Advance time + // temps t += dt; - // Periodic output + // sorties périodiques if (t + 1e-12 >= nextDump) { onDump(t, grid_, T_); nextDump += outEvery; From accb3514a6dc022b1a1417655a8b86933d696dc4 Mon Sep 17 00:00:00 2001 From: Broky64 Date: Mon, 1 Dec 2025 11:25:22 +0100 Subject: [PATCH 3/3] feat: Refactor output saving to use `std::filesystem` for dynamic directory creation and update gitignore with new temporary file exclusions. --- .gitignore | 6 +++- src/io.cpp | 14 +++++---- src/main.cpp | 85 ++++++++++++++++++++++++++-------------------------- 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index c014479..6809f88 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ # IDE .vscode/ .idea/ +.cache/ # Outputs /result/* @@ -21,4 +22,7 @@ .DS_Store # Logs -*.log \ No newline at end of file +*.log + +#Resources +.temp/ \ No newline at end of file diff --git a/src/io.cpp b/src/io.cpp index 3b0dd9f..1732cef 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -1,8 +1,9 @@ #include "io.hpp" -#include + #include -#include +#include #include +#include namespace fs = std::filesystem; @@ -23,9 +24,12 @@ void save_profile_csv(const std::string& path, if (x.size() != T.size()) throw std::runtime_error("save_profile_csv: vector size mismatch"); - // --- Ensure the result directory exists --- - fs::path fullPath = fs::path("result") / path; - fs::create_directories(fullPath.parent_path()); + // --- Ensure the directory exists --- + fs::path fullPath(path); + if (fullPath.has_parent_path()) + { + fs::create_directories(fullPath.parent_path()); + } // --- Open file for writing --- std::ofstream file(fullPath); diff --git a/src/main.cpp b/src/main.cpp index b45a499..426f1e3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,83 +4,82 @@ * saving temperature profiles as CSV files. */ -#include "types.hpp" #include "grid.hpp" -#include "solver.hpp" +#include "io.hpp" // <-- pour save_profile_csv() #include "method.hpp" +#include "solver.hpp" +#include "types.hpp" #include "user_input.h" -#include "io.hpp" // <-- pour save_profile_csv() - -#include +#include #include +#include #include #include -/** - * @brief Output callback: save temperature profiles to CSV at each dump time. - * - * The output folder is automatically created under `result/richardson/`. - * Example files: `result/richardson/t_0.00.csv`, `t_0.10.csv`, etc. - * - * @param t Current time [h] - * @param g Grid object (for x-locations) - * @param T Temperature field [°C] - */ -void save_to_csv(double t, const Grid& g, const std::vector& T) -{ - // Build file name like "t_0.10.csv" - std::ostringstream name; - name << "richardson/t_" << std::fixed << std::setprecision(2) << t << ".csv"; - - // Save profile to CSV using helper from io.cpp - save_profile_csv(name.str(), g.x, T); - - std::cout << "Saved: result/" << name.str() << "\n"; -} - /** * @brief Entry point for testing the Richardson explicit scheme. */ int main() { - try { + try + { std::cout << "=== 1D Heat Equation Solver (Richardson test) ===\n"; // --- Physical parameters (assignment constants) --- PhysParams phys; - phys.L_cm = 31.0; ///< Wall thickness [cm] - phys.Tin = 38.0; ///< Initial temperature [°C] - phys.Tsur = 149.0; ///< Boundary temperature [°C] - phys.D_cm2h = 93.0; ///< Diffusivity [cm²/h] + phys.L_cm = 31.0; ///< Wall thickness [cm] + phys.Tin = 38.0; ///< Initial temperature [°C] + phys.Tsur = 149.0; ///< Boundary temperature [°C] + phys.D_cm2h = 93.0; ///< Diffusivity [cm²/h] // --- Numerical parameters --- NumParams num; - num.dx = askForDouble("Enter Δx [cm] (suggested 0.05): ", - [](double v){ return v > 0.0 && v <= 1.0; }, - "Δx must be positive and <= 1.0 cm."); - num.dt = 0.01; ///< Time step [h] - num.tEnd = 0.5; ///< Simulation duration [h] - num.outEvery = 0.1; ///< Output interval [h] + num.dx = askForDouble( + "Enter Δx [cm] (suggested 0.05): ", + [](double v) { return v > 0.0 && v <= 1.0; }, + "Δx must be positive and <= 1.0 cm."); + num.dt = 0.01; ///< Time step [h] + num.tEnd = 0.5; ///< Simulation duration [h] + num.outEvery = 0.1; ///< Output interval [h] // --- Build grid --- Grid g(phys.L_cm, num.dx); validate_grid(g); - std::cout << "\nGrid created ✅ Nx = " << g.Nx - << ", dx = " << g.dx << " cm\n"; + std::cout << "\nGrid created ✅ Nx = " << g.Nx << ", dx = " << g.dx << " cm\n"; // --- Create solver with Richardson scheme --- HeatSolver solver(phys, num, SchemeKind::Richardson); - std::cout << "Running Richardson scheme...\n"; + + // --- Prepare output directory --- + namespace fs = std::filesystem; + std::string schemeName = "Richardson"; + fs::path outDir = fs::path("results") / schemeName; + if (!fs::exists(outDir)) + { + fs::create_directories(outDir); + } + + std::cout << "=== Running Richardson Solver ===\n"; + + auto onDump = [&](double t, const Grid& g, const std::vector& T) + { + std::ostringstream name; + name << "profile_t_" << std::fixed << std::setprecision(2) << t << "h.csv"; + fs::path fullPath = outDir / name.str(); + save_profile_csv(fullPath.string(), g.x, T); + std::cout << "Saved " << fullPath.string() << "\n"; + }; // --- Run simulation and save results --- - solver.run(save_to_csv); + solver.run(onDump); std::cout << "\nSimulation completed successfully ✅\n"; std::cout << "Results available under ./result/richardson/\n"; return 0; } - catch (const std::exception& e) { + catch (const std::exception& e) + { std::cerr << "Fatal error: " << e.what() << "\n"; return 1; }