1D Heat Equation Solver 1.0
Computational Methods Assignment 2025
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/**
2 * @file main.cpp
3 * @brief Test driver for the 1D heat conduction solver.
4 * Supports DuFort-Frankel, Richardson, Laasonen, and Crank-Nicolson schemes.
5 */
6
7#include "grid.hpp"
8#include "io.hpp"
9#include "method.hpp"
11#include "solver.hpp"
12#include "types.hpp"
13#include "user_input.h"
14#include <filesystem>
15#include <iomanip>
16#include <iostream>
17#include <sstream>
18#include <stdexcept>
19#include <string>
20#include <vector>
21
22namespace fs = std::filesystem;
23
24/**
25 * @brief Run a single simulation with the specified scheme.
26 *
27 * This helper function avoids code duplication by handling the solver setup,
28 * directory creation, and output callback for any given scheme.
29 *
30 * @param phys Physical parameters.
31 * @param num Numerical parameters.
32 * @param scheme The numerical scheme to use.
33 * @param schemeName String name for output directory (e.g. "Crank-Nicolson").
34 */
35void run_simulation(const PhysParams& phys,
36 const NumParams& num,
37 SchemeKind scheme,
38 const std::string& schemeName)
39{
40 std::cout << "\n=== Running " << schemeName << " Solver ===\n";
41
42 // --- Create solver ---
43 HeatSolver solver(phys, num, scheme);
44
45 // --- Prepare output directory ---
46 // Results will be saved in: results/<schemeName>/
47 fs::path outDir = fs::path("results") / schemeName;
48 if (!fs::exists(outDir))
49 {
50 fs::create_directories(outDir);
51 }
52
53 // --- Output callback ---
54 // Called by the solver at t=0 and every 'outEvery' hours.
55 auto onDump = [&](double t, const Grid& g, const std::vector<double>& T)
56 {
57 std::ostringstream name;
58 // Filename format: profile_t_0.10h.csv
59 name << "profile_t_" << std::fixed << std::setprecision(2) << t << "h.csv";
60
61 fs::path fullPath = outDir / name.str();
62 save_profile_csv(fullPath.string(), g.x, T);
63 };
64
65 // --- Run ---
66 solver.run(onDump);
67 std::cout << "Results saved to " << outDir << "\n";
68}
69
70/**
71 * @brief Main entry point for the 1D Heat Equation Solver.
72 *
73 * This function orchestrates the entire simulation workflow:
74 * 1. Defines physical and numerical parameters.
75 * 2. Validates the grid.
76 * 3. Prompts the user for method selection.
77 * 4. Executes the selected simulation(s).
78 *
79 * @return int 0 on success, 1 on error.
80 */
81int main()
82{
83 try
84 {
85 std::cout << "=== 1D Heat Equation Solver ===\n";
86
87 // ---------------------------------------------------------------------
88 // 1. Define Physical Parameters
89 // ---------------------------------------------------------------------
90 PhysParams phys;
91 phys.L_cm = 31.0;
92 phys.Tin = 38.0;
93 phys.Tsur = 149.0;
94 phys.D_cm2h = 93.0;
95
96 // ---------------------------------------------------------------------
97 // 2. Define Numerical Parameters
98 // ---------------------------------------------------------------------
99 NumParams num;
100
101 // Ask for dx once
102 num.dx = askForDouble(
103 "Enter Δx [cm] (suggested 0.05): ",
104 [](double v) { return v > 0.0 && v <= 1.0; },
105 "Δx must be positive and <= 1.0 cm.");
106
107 num.dt = 0.01; // Time step [h]
108 num.tEnd = 0.5; // Total duration [h]
109 num.outEvery = 0.1; // Output interval [h]
110
111 // ---------------------------------------------------------------------
112 // 3. Grid Validation (Early Check)
113 // ---------------------------------------------------------------------
114 Grid g(phys.L_cm, num.dx);
115 validate_grid(g);
116 std::cout << "Grid created ✅ Nx = " << g.Nx << ", dx = " << g.dx << " cm\n";
117
118 // ---------------------------------------------------------------------
119 // 4. Method Selection
120 // ---------------------------------------------------------------------
121 std::cout << "\nSelect numerical method:\n";
122 std::cout << "1. DuFort-Frankel\n";
123 std::cout << "2. Richardson\n";
124 std::cout << "3. Laasonen (Simple Implicit)\n";
125 std::cout << "4. Crank-Nicolson\n";
126 std::cout << "5. Run All (Batch + Analytical)\n";
127 std::cout << "6. Laasonen Stability Study (dt = 0.01, 0.025, 0.05, 0.1)\n";
128
129 // We use askForDouble (or askForInteger) to get a valid choice between 1 and 6
130 int choice = static_cast<int>(askForDouble(
131 "Enter choice (1-6): ",
132 [](double v) { return v >= 1.0 && v <= 6.0; },
133 "Please enter a number between 1 and 6."));
134
135 // ---------------------------------------------------------------------
136 // 5. Execution Logic
137 // ---------------------------------------------------------------------
138
139 // Case 1: DuFort-Frankel
140 if (choice == 1 || choice == 5)
141 {
142 run_simulation(phys, num, SchemeKind::DuFortFrankel, "DuFort-Frankel");
143 }
144
145 // Case 2: Richardson
146 if (choice == 2 || choice == 5)
147 {
148 run_simulation(phys, num, SchemeKind::Richardson, "Richardson");
149 }
150
151 // Case 3: Laasonen
152 if (choice == 3 || choice == 5)
153 {
154 run_simulation(phys, num, SchemeKind::Laasonen, "Laasonen");
155 }
156
157 // Case 4: Crank-Nicolson
158 if (choice == 4 || choice == 5)
159 {
160 run_simulation(phys, num, SchemeKind::CrankNicolson, "Crank-Nicolson");
161 }
162
163 // Case 5: Analytical Solution (Only in Batch or if we added a specific option)
164 if (choice == 5)
165 {
166 std::cout << "\n=== Computing Analytical Solution ===\n";
167 std::string outDir = "results/Analytical";
168 fs::create_directories(outDir);
169
170 // Generate profiles at same intervals as numerical methods
171 // t = 0, outEvery, 2*outEvery, ... tEnd
172 int steps = static_cast<int>(num.tEnd / num.outEvery);
173
174 // Explicitly export t=0.01 for comparison
175 {
176 double t = 0.01;
177 std::vector<double> T_analytical = Analytical::get_profile(t, g.x, phys);
178 std::ostringstream name;
179 name << outDir << "/profile_t_" << std::fixed << std::setprecision(2) << t
180 << "h.csv";
181 save_profile_csv(name.str(), g.x, T_analytical);
182 }
183
184 for (int i = 0; i <= steps; ++i)
185 {
186 double t = i * num.outEvery;
187 // Avoid tiny floating point errors
188 if (t > num.tEnd)
189 t = num.tEnd;
190
191 std::vector<double> T_analytical = Analytical::get_profile(t, g.x, phys);
192
193 std::ostringstream name;
194 name << outDir << "/profile_t_" << std::fixed << std::setprecision(2) << t
195 << "h.csv";
196 save_profile_csv(name.str(), g.x, T_analytical);
197 }
198 std::cout << "Results saved to " << outDir << "\n";
199 }
200
201 // Case 6: Laasonen Stability Study
202 if (choice == 6)
203 {
204 std::cout << "\n=== Running Laasonen Stability Study ===\n";
205 std::vector<double> dts = {0.01, 0.025, 0.05, 0.1};
206
207 for (double dt : dts)
208 {
209 // Create a local copy of params to modify dt
210 NumParams localNum = num;
211 localNum.dt = dt;
212
213 std::ostringstream name;
214 name << "Laasonen_dt_" << std::fixed << std::setprecision(3) << dt;
215 // Remove trailing zeros/dot if desired, or keep fixed precision for sorting
216 // "Laasonen_dt_0.010", etc.
217
218 run_simulation(phys, localNum, SchemeKind::Laasonen, name.str());
219 }
220 }
221
222 std::cout << "\nAll requested simulations completed successfully ✅\n";
223 return 0;
224 }
225 catch (const std::exception& e)
226 {
227 std::cerr << "Fatal error: " << e.what() << "\n";
228 return 1;
229 }
230}
static std::vector< double > get_profile(double t, const std::vector< double > &x_grid, const PhysParams &phys)
Generate a full temperature profile for a set of grid points.
void run(const std::function< void(double, const Grid &, const std::vector< double > &)> &onDump)
Run the simulation from t=0 to t=tEnd.
Definition solver.cpp:83
Uniform 1D spatial grid.
bool validate_grid(const Grid &g, double tol=1e-12)
Validate grid consistency (assertions in Debug mode).
Definition grid.cpp:26
CSV utilities.
void save_profile_csv(const std::string &path, const std::vector< double > &x, const std::vector< double > &T)
Save a temperature profile (x, T) to a CSV file.
Definition io.cpp:20
void run_simulation(const PhysParams &phys, const NumParams &num, SchemeKind scheme, const std::string &schemeName)
Run a single simulation with the specified scheme.
Definition main.cpp:35
int main()
Main entry point for the 1D Heat Equation Solver.
Definition main.cpp:81
SchemeKind
Enumeration of the numerical schemes available in the solver.
Definition method.hpp:13
@ Laasonen
Simple Implicit (forward time, central space).
Definition method.hpp:16
@ DuFortFrankel
Modified Richardson (explicit, stable).
Definition method.hpp:15
@ CrankNicolson
Trapezoidal Implicit (second order accuracy).
Definition method.hpp:17
@ Richardson
Central time, central space (explicit, unstable).
Definition method.hpp:14
High-level solver orchestrating BCs and time integration.
Uniform 1D grid on x ∈ [0, L], including both boundary nodes.
Definition grid.hpp:15
std::size_t Nx
Number of nodes (including boundaries).
Definition grid.hpp:18
double dx
Spatial step [cm].
Definition grid.hpp:17
std::vector< double > x
Node coordinates [cm].
Definition grid.hpp:19
Numerical parameters controlling the discretization and output.
Definition types.hpp:20
double dx
Spatial step [cm].
Definition types.hpp:21
double outEvery
Output interval [h].
Definition types.hpp:24
double tEnd
Final time [h].
Definition types.hpp:23
double dt
Time step [h].
Definition types.hpp:22
Physical parameters of the wall heat conduction problem.
Definition types.hpp:10
double Tin
Initial temperature [°C].
Definition types.hpp:13
double D_cm2h
Thermal diffusivity [cm^2/h].
Definition types.hpp:12
double L_cm
Wall thickness [cm].
Definition types.hpp:11
double Tsur
Surface (Dirichlet) temperature [°C].
Definition types.hpp:14
Basic physical and numerical parameter types for the 1D heat problem.
Utility functions for safe user input from the console.
double askForDouble(const std::string &prompt, const std::function< bool(double)> &isValid, const std::string &errorMessage)
Ask for a floating-point value from the user.