Add project 1 with the corresponding source code and report.

This commit is contained in:
2025-08-31 14:03:13 +02:00
commit 49a519379c
18 changed files with 2421 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
echo "Cleaning up old results..."
rm -f poisson_solution.csv numerical_solution_*.csv rel_errors.txt timing_results_100_iterations.csv
rm -f poisson_solver poisson_timing poisson
rm -f ../../projects/project1/include/*.pdf
echo "Compiling the C++ code..."
g++ -o poisson_solver poisson_solver.cpp src/*
g++ -o poisson_timing poisson_timing.cpp src/*
g++ -o poisson poisson.cpp
echo "Compilation finished."
echo "Generating reference solution..."
./poisson
uv run python/poisson_plotter.py --filename "poisson_solution.csv"
for power in 1 2 3 4 5 6 7; do
N=$((10 ** power))
if [ $power -lt 5 ]; then
output_to_file=true
else
output_to_file=false
fi
echo "Running simulation for N=$N (output: $output_to_file)"
./poisson_solver $N $output_to_file | tee >(awk '
/Running Poisson solver/ { N=$7 }
/Max relative error/ { print N"&"$4"\\\\" >> "rel_errors.txt" }
')
if [ "$output_to_file" = true ]; then
uv run python/poisson_plotter.py --filename "numerical_solution_$N.csv"
uv run python/poisson_plotter.py --filename "numerical_solution_$N.csv" --reference "poisson_solution.csv"
fi
done
echo "Creating Summary Plot..."
uv run python/poisson_plotter.py --reference "poisson_solution.csv" --filename "numerical_solution_*.csv"
echo "Timing the algorithm variations..."
./poisson_timing
uv run python/timing_plotter.py "timing_results_100_iterations.csv"
echo "All done!"
+10
View File
@@ -0,0 +1,10 @@
#include <vector>
#include <cmath>
#include <iostream>
double f(double x);
std::vector<double> get_g(const std::vector<double>& x);
std::vector<double> get_x(int N);
std::vector<double> analytical_solution(const std::vector<double>& x);
double relative_error(const std::vector<double>& v, const std::vector<double>& u);
void debug_print(const std::vector<double>& v, const std::string& name);
+6
View File
@@ -0,0 +1,6 @@
#include <vector>
std::vector<double> general_algorithm(const std::vector<double>& a, const std::vector<double>& b, const std::vector<double>& c, const std::vector<double>& g);
std::vector<double> special_algorithm(const std::vector<double>& g);
std::vector<double> optimal_algorithm_opt(const std::vector<double>& g);
std::vector<double> add_boundaries(const std::vector<double>& v);
+24
View File
@@ -0,0 +1,24 @@
#include <iostream>
#include <fstream>
#include <cmath>
#include <iomanip>
using namespace std;
int main(){
const double dx = 1.0/1000000;
const double xmin = 0;
const double xmax = 1;
const int decimals = 14;
string output_file = "poisson_solution.csv";
ofstream ofs(output_file);
ofs << "x,u(x)\n";
for(double x = xmin; x <= xmax; x += dx){
double u = 1 - (1 - exp(-10))*x - exp(-10*x);
ofs << fixed << setprecision(decimals) << x << "," << u << "\n";
}
ofs.close();
return 0;
}
+56
View File
@@ -0,0 +1,56 @@
#include <iomanip>
#include <fstream>
#include <cstring>
#include "include/solvers.hpp"
#include "include/helpers.hpp"
using namespace std;
int main(int argc, char* argv[]){
const int decimal_places = 14;
int N; // !! Number of steps between the discretization points (= N_points - 1)
if (argc > 1) { // Get number of steps from command line argument
N = atoi(argv[1]);
} else {
N = 1000;
}
bool output_to_file = true;
if (argc > 2) { // Get output preference from command line argument (use "true" or "false", default is true)
output_to_file = (strcmp(argv[2], "true") == 0);
}
const double delta_x = 1.0 / N;
vector<double> a(N-1, -1.0);
vector<double> b(N-1, 2.0);
vector<double> c(N-1, -1.0);
vector<double> g(N-1, 0.0);
cout << "Running Poisson solver with N = " << N << endl;
for (int i = 1; i < N; i++) {
double x = i * delta_x;
g[i-1] = delta_x * delta_x * f(x);
}
vector<double> solution = add_boundaries(general_algorithm(a, b, c, g));
vector<double> x = get_x(N);
vector<double> u = analytical_solution(x);
double max_rel_error = relative_error(solution, u);
cout << "Max relative error: " << max_rel_error << endl;
if (output_to_file) {
// Output the solution
string filename = "numerical_solution_" + to_string(N) + ".csv";
ofstream ofs(filename);
ofs << "x,u(x)" << endl;
for (int i = 0; i <= N; i++) {
ofs << fixed << setprecision(decimal_places) << scientific << x[i] << "," << solution[i] << endl;
}
ofs.close();
}
return 0;
}
+58
View File
@@ -0,0 +1,58 @@
#include "include/helpers.hpp"
#include "include/solvers.hpp"
#include <chrono>
#include <fstream>
#include <iomanip>
using namespace std;
vector<double> appl_general_algorithm(const vector<double>& g){
int N = g.size() + 1;
vector<double> a(N-1, -1.0);
vector<double> b(N-1, 2.0);
vector<double> c(N-1, -1.0);
return general_algorithm(a, b, c, g);
}
double time_function(const vector<double>& g, vector<double> (*func)(const vector<double>&), const int n_iter=100){
auto start = chrono::high_resolution_clock::now();
for(int i = 0; i < n_iter; i++){
func(g);
}
auto end = chrono::high_resolution_clock::now();
chrono::duration<double> elapsed = end - start;
return elapsed.count() / n_iter;
}
int main(int argc, char* argv[]) {
int n_iter = 100;
if (argc > 1) { // Get number of iterations from command line argument
n_iter = atoi(argv[1]);
}
int N = 1000;
vector<double> g = get_g(get_x(N));
cout << "Relative error between solvers: " << relative_error(special_algorithm(g), appl_general_algorithm(g)) << endl;
cout << "Relative error between solvers (optimized): " << relative_error(optimal_algorithm_opt(g), appl_general_algorithm(g)) << endl;
string filename = "timing_results_" + to_string(n_iter) + "_iterations.csv";
ofstream outfile(filename);
outfile << "N,Time (Special),Time (Optimized),Time (General)\n";
for(int power = 1; power <= 6; power++){
N = pow(10, power);
g = get_g(get_x(N));
double time_optimal = time_function(g, special_algorithm, n_iter);
double time_optimal_opt = time_function(g, optimal_algorithm_opt, n_iter);
double time_general = time_function(g, appl_general_algorithm, n_iter);
cout << "Average time for specialized algorithm over " << N << " values: " << time_optimal << " seconds" << endl;
cout << "Average time for optimized algorithm over " << N << " values: " << time_optimal_opt << " seconds" << endl;
cout << "Average time for general algorithm over " << N << " values: " << time_general << " seconds" << endl;
outfile << N << "," << scientific << setprecision(10) << time_optimal << "," << time_optimal_opt << "," << time_general << endl;
}
outfile.close();
return 0;
}
+155
View File
@@ -0,0 +1,155 @@
from importlib.resources import files
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import argparse
import os
parser = argparse.ArgumentParser()
parser.add_argument("--filename", type=str, default="src/project1/poisson_solution.csv")
parser.add_argument("--reference", type=str, default=None)
args = parser.parse_args()
FILENAME = args.filename
REFERENCE = args.reference
def singular_plot():
df = pd.read_csv(FILENAME)
fig, ax = plt.subplots(figsize=(6,3))
ax.plot(df["x"], df["u(x)"])
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$u(x)$")
fig.tight_layout()
output_directory = os.path.abspath(os.path.join(__file__, "../../../../projects/project1/include"))
output_file = os.path.basename(FILENAME).replace(".csv", ".pdf")
fig.savefig(os.path.join(output_directory, output_file))
def reference_plot():
df = pd.read_csv(FILENAME).sort_values("x")
df_ref = pd.read_csv(REFERENCE).sort_values("x")
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True, height_ratios=[3, 1], figsize=(4, 8))
ax1.plot(df["x"], df["u(x)"], label="Numerical Solution")
ax1.plot(df_ref["x"], df_ref["u(x)"], label="Reference Solution", linestyle="--")
ax1.set_xlabel(r"$x$")
ax1.set_ylabel(r"$u(x)$")
ax1.legend()
combined_df = pd.merge_asof(df, df_ref, on="x", suffixes=("_num", "_ref"))
ratio = (combined_df["u(x)_num"] - combined_df["u(x)_ref"]) / combined_df["u(x)_ref"]
ax2.plot(combined_df["x"], ratio.abs())
ax2.set_xlabel(r"$x$")
ax2.set_ylabel(r"$|\frac{u(x)_{num} - u(x)_{ref}}{u(x)_{ref}}|$")
ax2.axhline(y=1, color="gray", linestyle="--")
ax2.set_yscale("log")
fig.tight_layout()
output_directory = os.path.abspath(os.path.join(__file__, "../../../../projects/project1/include"))
output_file = os.path.basename(FILENAME).replace(".csv", "_ref.pdf")
fig.savefig(os.path.join(output_directory, output_file))
def multiple_reference_plot():
# Filename is wildcard -> Find files
import glob
files = glob.glob(FILENAME)
files.sort()
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True, height_ratios=[3, 1], figsize=(6,5))
df_ref = pd.read_csv(REFERENCE).sort_values("x")
for file in files:
df = pd.read_csv(file).sort_values("x")
label = f"Num. (N = {os.path.basename(file).replace(".csv", "").split("_")[-1]})"
ax1.plot(df["x"], df["u(x)"], label=label)
combined_df = pd.merge_asof(df, df_ref, on="x", suffixes=("_num", "_ref"))
ratio = (combined_df["u(x)_num"] - combined_df["u(x)_ref"]) / combined_df["u(x)_ref"]
ax2.plot(combined_df["x"], ratio.abs(), label=label)
ax1.plot(df_ref["x"], df_ref["u(x)"], label="Reference Solution", linestyle="--", color="gray")
ax1.set_xlabel(r"$x$")
ax1.set_ylabel(r"$u(x)$")
ax1.legend()
ax2.axhline(y=1, color="gray", linestyle="--")
ax2.set_xlabel(r"$x$")
ax2.set_ylabel(r"$|\frac{u(x)_{num} - u(x)_{ref}}{u(x)_{ref}}|$")
ax2.set_yscale("log")
fig.tight_layout()
output_directory = os.path.abspath(os.path.join(__file__, "../../../../projects/project1/include"))
output_file = "big_ref_plot.pdf"
fig.savefig(os.path.join(output_directory, output_file))
def error_plots():
abs_error_plot()
rel_error_plot()
def abs_error_plot():
import glob
files = glob.glob(FILENAME)
files.sort()
df_ref = pd.read_csv(REFERENCE).sort_values("x")
fig, ax = plt.subplots(figsize=(6,3))
for file in files:
df = pd.read_csv(file).sort_values("x")
combined_df = pd.merge_asof(df, df_ref, on="x", suffixes=("_num", "_ref"))
log_abs_err = np.log10((combined_df["u(x)_num"] - combined_df["u(x)_ref"]).abs())
ax.plot(combined_df["x"], log_abs_err, label=f"N={os.path.basename(file).replace('.csv','').split('_')[-1]}")
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$\log_{10}(|u(x)_{num} - u(x)_{ref}|)$")
ax.legend()
fig.tight_layout()
output_directory = os.path.abspath(os.path.join(__file__, "../../../../projects/project1/include"))
output_file = "abs_error_plot.pdf"
fig.savefig(os.path.join(output_directory, output_file))
def rel_error_plot():
import glob
files = glob.glob(FILENAME)
files.sort()
df_ref = pd.read_csv(REFERENCE).sort_values("x")
fig, ax = plt.subplots(figsize=(6,3))
for file in files:
df = pd.read_csv(file).sort_values("x")
combined_df = pd.merge_asof(df, df_ref, on="x", suffixes=("_num", "_ref"))
# Drop rows where u(x)_ref is zero
combined_df = combined_df[combined_df["u(x)_ref"].abs() > 1e-10]
log_rel_err = np.log10((combined_df["u(x)_num"] - combined_df["u(x)_ref"]).abs() / combined_df["u(x)_ref"].abs())
ax.plot(combined_df["x"], log_rel_err, label=f"N={os.path.basename(file).replace('.csv','').split('_')[-1]}")
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$\log_{10}(|\frac{u(x)_{num} - u(x)_{ref}}{u(x)_{ref}}|)$")
ax.legend()
fig.tight_layout()
output_directory = os.path.abspath(os.path.join(__file__, "../../../../projects/project1/include"))
output_file = "rel_error_plot.pdf"
fig.savefig(os.path.join(output_directory, output_file))
if __name__ == "__main__":
if REFERENCE:
if "*" in FILENAME:
multiple_reference_plot()
abs_error_plot()
rel_error_plot()
else:
reference_plot()
else:
singular_plot()
+30
View File
@@ -0,0 +1,30 @@
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import argparse
import os
parser = argparse.ArgumentParser(description="Plot timing results from CSV file.")
parser.add_argument("csv_file", help="Path to the CSV file containing timing results.")
args = parser.parse_args()
df = pd.read_csv(args.csv_file)
for column in df.columns[1:]:
df[column] = df[column] / df["N"] # Normalize by N
fig, ax = plt.subplots(figsize=(6,3))
x = np.arange(len(df))
bar_width = 0.2
for i, column in enumerate(df.columns[1:]):
ax.bar(x + i * bar_width, df[column], width=bar_width, label=column)
ax.set_xticks(x + bar_width / 2)
ax.set_xticklabels(df["N"])
ax.set_xlabel("N (number of values)")
ax.set_ylabel("Time (seconds) per N")
ax.legend(ncol=3)
fig.tight_layout()
output_directory = os.path.abspath(os.path.join(__file__, "../../../../projects/project1/include"))
output_file = os.path.basename(args.csv_file).replace(".csv", ".pdf")
fig.savefig(os.path.join(output_directory, output_file))
+57
View File
@@ -0,0 +1,57 @@
#include "../include/helpers.hpp"
double f(double x){
return 100*exp(-10*x);
}
std::vector<double> get_x(int N){
std::vector<double> x;
for (int i = 0; i <= N; i++) {
x.push_back(i * (1.0 / N));
}
return x;
}
std::vector<double> get_g(const std::vector<double>& x){
std::vector<double> g;
double delta_x = x[1] - x[0];
for (int i = 1; i < x.size() - 1; i++) {
double x_i = x[i];
g.push_back(delta_x * delta_x * f(x_i));
}
return g;
}
std::vector<double> analytical_solution(const std::vector<double>& x){
std::vector<double> u;
for(int i = 0; i < x.size(); i++){
double x_i = x.at(i);
double u_i = 1.0 - (1.0 - exp(-10.0))*x_i - exp(-10.0*x_i);
u.push_back(u_i);
}
return u;
}
double relative_error(const std::vector<double>& v, const std::vector<double>& u){
if (v.size() != u.size()) {
std::cerr << "Error: Vectors must be of the same size." << std::endl << "Found sizes: " << v.size() << " and " << u.size() << std::endl;
return -1.0;
}
const double eps = 1e-14;
double max_rel_error = 0.0;
for(int i = 0; i < v.size(); i++){
if (fabs(u[i]) > eps) { // Avoid division by zero
double rel_error = fabs((v[i] - u[i]) / u[i]);
if (rel_error > max_rel_error) {
max_rel_error = rel_error;
}
}
}
return max_rel_error;
}
void debug_print(const std::vector<double>& v, const std::string& name){
for (int i = 0; i < v.size(); i++){
std::cout << name << "[" << i << "] = " << v[i] << std::endl;
}
}
+83
View File
@@ -0,0 +1,83 @@
#include "../include/solvers.hpp"
#include<iostream>
std::vector<double> general_algorithm(const std::vector<double>& a, const std::vector<double>& b, const std::vector<double>& c, const std::vector<double>& g) {
int n = g.size();
std::vector<double> c_prime(n-1, 0.0);
std::vector<double> g_prime(n, 0.0);
// Forward elimination
c_prime[0] = c[0] / b[0];
for(int i = 1; i < n - 1; i++){
c_prime[i] = c[i] / (b[i] - a[i] * c_prime[i - 1]);
}
g_prime[0] = g[0] / b[0];
for (int i = 1; i < n; i++) {
g_prime[i] = (g[i] - a[i] * g_prime[i - 1]) / (b[i] - a[i] * c_prime[i - 1]);
}
// Back substitution
std::vector<double> v(n, 0.0);
v[n - 1] = g_prime[n - 1];
for (int i = n - 2; i >= 0; i--) {
v[i] = g_prime[i] - c_prime[i] * v[i + 1];
}
return v;
}
std::vector<double> special_algorithm(const std::vector<double>& g){
int n = g.size();
std::vector<double> v(n, 0.0);
std::vector<double> g_tilde(n, 0.0);
g_tilde[0] = g[0] / 2.0;
for(int i = 1; i <= n - 1; i++){
g_tilde[i] = (g[i] + g_tilde[i - 1]) * double(i + 1) / double(i + 2);
}
v[n - 1] = g_tilde[n - 1];
for(int i = n - 2; i >= 0; i--){
v[i] = g_tilde[i] + double(i + 1) / double(i + 2) * v[i + 1];
}
return v;
}
std::vector<double> optimal_algorithm_opt(const std::vector<double>& g) {
// Trying to shave off the last few bits here and there
const int n = static_cast<int>(g.size());
std::vector<double> v(n);
// Use g_tilde in-place (reuse v as workspace to save one allocation)
double prev = g[0] * 0.5;
v[0] = prev;
// Forward sweep
for (int i = 1; i < n; i++) {
const double factor = double(i + 1) / double(i + 2);
prev = (g[i] + prev) * factor;
v[i] = prev;
}
// Backward sweep
double next = v[n - 1];
for (int i = n - 2; i >= 0; i--) {
const double factor = double(i + 1) / double(i + 2);
next = v[i] + factor * next;
v[i] = next;
}
return v;
}
std::vector<double> add_boundaries(const std::vector<double>& v){
std::vector<double> v_ast;
// Add 0.0 to the beginning and end
v_ast.push_back(0.0);
for (int i = 0; i < v.size(); i++){
v_ast.push_back(v[i]);
}
v_ast.push_back(0.0);
return v_ast;
}