From 55adc41e616845a7ca7680326405caab9c8812bc Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Fri, 4 Feb 2022 09:57:28 +0100 Subject: [PATCH 01/51] Refactor code and rebase code to solve LES into function. - Also created new condition if dimension is 2 @ simulate --- src/BTCSDiffusion.cpp | 23 +++++++++++++++-------- src/BTCSDiffusion.hpp | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 92855dc..7f121e2 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -129,14 +129,7 @@ void BTCSDiffusion::simulate1D(std::vector &c, boundary_condition left, b_vector[i] = -c[j]; } - // start to solve - Eigen::SparseLU, Eigen::COLAMDOrdering> - solver; - solver.analyzePattern(A_matrix); - - solver.factorize(A_matrix); - - x_vector = solver.solve(b_vector); + solveLES(); //fill solution back in place into =c= vector for (int i = 0, j = i + !left_is_constant; i < c.size(); i++, j++) { @@ -154,6 +147,9 @@ void BTCSDiffusion::simulate(std::vector &c, simulate1D(c, bc[0], bc[grid_cells[0] + 1], alpha, this->deltas[0], this->grid_cells[0]); } + if (this->grid_dim == 2) { + + } } inline double BTCSDiffusion::getBCFromFlux(boundary_condition bc, @@ -179,3 +175,14 @@ void BTCSDiffusion::setBoundaryCondition(int index, bctype type, double value) { bc[index].type = type; bc[index].value = value; } + +inline void BTCSDiffusion::solveLES() { + // start to solve + Eigen::SparseLU, Eigen::COLAMDOrdering> + solver; + solver.analyzePattern(A_matrix); + + solver.factorize(A_matrix); + + x_vector = solver.solve(b_vector); +} diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index ea3473d..88db9f7 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -142,6 +142,7 @@ private: void simulate3D(std::vector &c); inline double getBCFromFlux(boundary_condition bc, double nearest_value, double neighbor_alpha); + inline void solveLES(); void updateInternals(); std::vector bc; From f4253f2e6ab3fe37f14bd76dde206ae75bb3f874 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 8 Feb 2022 11:22:18 +0100 Subject: [PATCH 02/51] Replace internal handling of vectors by Eigen library members --- src/BTCSDiffusion.cpp | 8 ++++++-- src/BTCSDiffusion.hpp | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 92855dc..870479c 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include #include @@ -68,7 +70,7 @@ void BTCSDiffusion::updateInternals() { bc.resize(cells, {BTCSDiffusion::BC_CLOSED, 0}); } -void BTCSDiffusion::simulate1D(std::vector &c, boundary_condition left, +void BTCSDiffusion::simulate1D(Eigen::Map &c, boundary_condition left, boundary_condition right, const std::vector &alpha, double dx, int size) { @@ -151,7 +153,9 @@ void BTCSDiffusion::setTimestep(double time_step) { void BTCSDiffusion::simulate(std::vector &c, const std::vector &alpha) { if (this->grid_dim == 1) { - simulate1D(c, bc[0], bc[grid_cells[0] + 1], alpha, this->deltas[0], + assert(c.size() == grid_cells[0]); + Eigen::Map c_in(c.data(), this->grid_cells[0]); + simulate1D(c_in, bc[0], bc[grid_cells[0] + 1], alpha, this->deltas[0], this->grid_cells[0]); } } diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index ea3473d..d44a407 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -2,6 +2,8 @@ #define BTCSDIFFUSION_H_ #include +#include +#include #include #include #include @@ -135,7 +137,7 @@ private: } boundary_condition; typedef Eigen::Triplet T; - void simulate1D(std::vector &c, boundary_condition left, + void simulate1D(Eigen::Map &c, boundary_condition left, boundary_condition right, const std::vector &alpha, double dx, int size); void simulate2D(std::vector &c); From 8de34ad65b1213e45247ccc756121f271b9c4cad Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 8 Feb 2022 11:26:54 +0100 Subject: [PATCH 03/51] Replace copying of output vector by oneliner --- src/BTCSDiffusion.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 870479c..ceef611 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -70,7 +70,8 @@ void BTCSDiffusion::updateInternals() { bc.resize(cells, {BTCSDiffusion::BC_CLOSED, 0}); } -void BTCSDiffusion::simulate1D(Eigen::Map &c, boundary_condition left, +void BTCSDiffusion::simulate1D(Eigen::Map &c, + boundary_condition left, boundary_condition right, const std::vector &alpha, double dx, int size) { @@ -78,10 +79,10 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, boundary_conditio bool left_is_constant = (left.type == BTCSDiffusion::BC_CONSTANT); bool right_is_constant = (right.type == BTCSDiffusion::BC_CONSTANT); - //The sizes for matrix and vectors of the equation system is defined by the - //actual size of the input vector and if the system is (partially) closed. - //Then we will need ghost nodes. So this variable will give the count of ghost - //nodes. + // The sizes for matrix and vectors of the equation system is defined by the + // actual size of the input vector and if the system is (partially) closed. + // Then we will need ghost nodes. So this variable will give the count of + // ghost nodes. int bc_offset = !left_is_constant + !right_is_constant; ; @@ -140,10 +141,8 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, boundary_conditio x_vector = solver.solve(b_vector); - //fill solution back in place into =c= vector - for (int i = 0, j = i + !left_is_constant; i < c.size(); i++, j++) { - c[i] = x_vector[i + !left_is_constant]; - } + // write back result to input/output vector + c = x_vector.segment(!left_is_constant, c.size()); } void BTCSDiffusion::setTimestep(double time_step) { From d4b6a95bc3d999a04b9052a234ca787f85e7d0cf Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 8 Feb 2022 13:01:18 +0100 Subject: [PATCH 04/51] Implement function to fill A matrix from one row of input --- src/BTCSDiffusion.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++ src/BTCSDiffusion.hpp | 10 +++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 53667d2..f4888ee 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -138,6 +138,50 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, c = x_vector.segment(!left_is_constant, c.size()); } +void BTCSDiffusion::simulate2D(Eigen::Map &c, + Eigen::Map &alpha) { + + int n_cols = c.cols(); + unsigned int size = (this->grid_cells[0] + 2) * (this->grid_cells[1]); + + A_matrix.resize(size, size); + A_matrix.reserve(Eigen::VectorXi::Constant(size, 3)); + + for (int i = 0; i < c.rows(); i++) { + bool left = bc[i*n_cols].type == BTCSDiffusion::BC_CONSTANT; + bool right = bc[((i+1)*n_cols)-1].type == BTCSDiffusion::BC_CONSTANT; + fillMatrixFromRow(alpha, i, left, right, domain_size[0]); + } +} + +inline void +BTCSDiffusion::fillMatrixFromRow(Eigen::Map &alpha, + int row, bool left_constant, + bool right_constant, int delta) { + + int n_cols = A_matrix.cols(); + int offset = n_cols * row; + + A_matrix.insert(offset, offset) = !left_constant; + + if (left_constant) + A_matrix.insert(offset + 1, offset + 1) = 1; + + A_matrix.insert(offset + (n_cols - 1), offset + (n_cols - 1)) = + !right_constant; + + if (right_constant) + A_matrix.insert(offset + (n_cols - 2), offset + (n_cols - 2)) = 1; + + for (int j = 1 + left_constant; j < offset - (1 - right_constant); j++) { + double sx = (alpha(row, j) * this->time_step) / (delta * delta); + + A_matrix.insert(offset + j, offset + j) = -1. - 2. * sx; + A_matrix.insert(offset + j, offset + (j - 1)) = sx; + A_matrix.insert(offset + j, offset + (j + 1)) = sx; + } +} + void BTCSDiffusion::setTimestep(double time_step) { this->time_step = time_step; } @@ -151,7 +195,12 @@ void BTCSDiffusion::simulate(std::vector &c, this->grid_cells[0]); } if (this->grid_dim == 2) { + assert(c.size() == grid_cells[0] * grid_cells[1]); + Eigen::Map c_in(c.data(), this->grid_cells[1], + this->grid_cells[0]); + Eigen::Map alpha_in( + alpha.data(), this->grid_cells[1], this->grid_cells[0]); } } diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 072dff8..f6be297 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -140,11 +140,17 @@ private: void simulate1D(Eigen::Map &c, boundary_condition left, boundary_condition right, const std::vector &alpha, double dx, int size); - void simulate2D(std::vector &c); + void simulate2D(Eigen::Map &c, + Eigen::Map &alpha); + + inline void fillMatrixFromRow(Eigen::Map &alpha, + int row, bool left_constant, + bool right_constant, int delta); + void simulate3D(std::vector &c); inline double getBCFromFlux(boundary_condition bc, double nearest_value, double neighbor_alpha); - inline void solveLES(); + inline void solveLES(); void updateInternals(); std::vector bc; From 3f2ff635ea746cbf44fa778fa6b653e68c92cfc7 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 10 Feb 2022 12:48:16 +0100 Subject: [PATCH 05/51] index on 2D: d4b6a95 Implement function to fill A matrix from one row of input From dc5bc42bb881f01eb14f6a5246a66d04829f868a Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 10 Feb 2022 15:08:41 +0100 Subject: [PATCH 06/51] Remove unnecessary output --- src/BTCSDiffusion.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 3494b5f..94913af 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -299,17 +299,12 @@ void BTCSDiffusion::setBoundaryCondition(int index, bctype type, double value) { } inline void BTCSDiffusion::solveLES() { - - std::cout << A_matrix << std::endl; - // start to solve Eigen::SparseLU, Eigen::COLAMDOrdering> solver; solver.analyzePattern(A_matrix); - solver.factorize(A_matrix); - std::cout << solver.lastErrorMessage() << " HHHHH" << std::endl; x_vector = solver.solve(b_vector); } From cda16b774455641e70d4de47a7242c8a8e4cb6a3 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 10 Feb 2022 15:34:07 +0100 Subject: [PATCH 07/51] Attempt to solve write back to c vector input - Solution already looks good to me (in x-direction) --- src/BTCSDiffusion.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 94913af..090ba83 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -166,11 +167,22 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, solveLES(); - x_vector.conservativeResize(c.rows(), c.cols() + 2); + Eigen::MatrixXd test = x_vector; - // std::cout << x_vector << std::endl; + std::cout << test << std::endl; - c = x_vector.block(0, 1, c.rows(), c.cols()); + test.transposeInPlace(); + test.conservativeResize(c.rows(), c.cols() + 2); + + std::cout << test << std::endl; + + Eigen::Map tmp(test.data(), c.rows(), c.cols() +2); + + std::cout << x_vector << std::endl; + std::cout << tmp << std::endl; + + + c = tmp.block(0, 1, c.rows(), c.cols()); } void BTCSDiffusion::fillMatrixFromRow(const Eigen::VectorXd &alpha, int n_cols, @@ -181,13 +193,13 @@ void BTCSDiffusion::fillMatrixFromRow(const Eigen::VectorXd &alpha, int n_cols, n_cols += 2; int offset = n_cols * row; - A_matrix.insert(offset, offset) = !left_constant; + A_matrix.insert(offset, offset) = 1; if (left_constant) A_matrix.insert(offset + 1, offset + 1) = 1; A_matrix.insert(offset + (n_cols - 1), offset + (n_cols - 1)) = - !right_constant; + 1; if (right_constant) A_matrix.insert(offset + (n_cols - 2), offset + (n_cols - 2)) = 1; From 05d3cfdc3c8f8e5d801338c68064b5e21bdb5c6b Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Fri, 11 Feb 2022 13:43:53 +0100 Subject: [PATCH 08/51] Implemented first step of 2D ADI-BTCSDiffusion - defined important matrices + vectors as row-major matrices --- src/BTCSDiffusion.cpp | 41 ++++++++++++++++------------------------- src/BTCSDiffusion.hpp | 20 +++++++++++++------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 090ba83..6295bfe 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -74,7 +74,7 @@ void BTCSDiffusion::updateInternals() { bc.resize(cells, {BTCSDiffusion::BC_CLOSED, 0}); } -void BTCSDiffusion::simulate1D(Eigen::Map &c, +void BTCSDiffusion::simulate1D(Eigen::Map &c, boundary_condition left, boundary_condition right, const std::vector &alpha, double dx, @@ -142,8 +142,10 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, c = x_vector.segment(!left_is_constant, c.size()); } -void BTCSDiffusion::simulate2D(Eigen::Map &c, - Eigen::Map &alpha) { +void BTCSDiffusion::simulate2D(Eigen::Map &c, + Eigen::Map &alpha) { + + DMatrixRowMajor tmp_vector = x_vector; int n_cols = c.cols(); unsigned int size = (this->grid_cells[0] + 2) * (this->grid_cells[1]); @@ -167,25 +169,15 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, solveLES(); - Eigen::MatrixXd test = x_vector; + tmp_vector.transposeInPlace(); + tmp_vector.conservativeResize(c.rows(), c.cols() + 2); - std::cout << test << std::endl; + Eigen::Map tmp(tmp_vector.data(), c.rows(), c.cols() + 2); - test.transposeInPlace(); - test.conservativeResize(c.rows(), c.cols() + 2); - - std::cout << test << std::endl; - - Eigen::Map tmp(test.data(), c.rows(), c.cols() +2); - - std::cout << x_vector << std::endl; - std::cout << tmp << std::endl; - - - c = tmp.block(0, 1, c.rows(), c.cols()); + c = tmp_vector.block(0, 1, c.rows(), c.cols()); } -void BTCSDiffusion::fillMatrixFromRow(const Eigen::VectorXd &alpha, int n_cols, +void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, int row, bool left_constant, bool right_constant, double delta, double time_step) { @@ -198,14 +190,13 @@ void BTCSDiffusion::fillMatrixFromRow(const Eigen::VectorXd &alpha, int n_cols, if (left_constant) A_matrix.insert(offset + 1, offset + 1) = 1; - A_matrix.insert(offset + (n_cols - 1), offset + (n_cols - 1)) = - 1; + A_matrix.insert(offset + (n_cols - 1), offset + (n_cols - 1)) = 1; if (right_constant) A_matrix.insert(offset + (n_cols - 2), offset + (n_cols - 2)) = 1; for (int j = 1 + left_constant; j < n_cols - (1 - right_constant); j++) { - double sx = (alpha[j-1] * time_step) / (delta * delta); + double sx = (alpha[j - 1] * time_step) / (delta * delta); if (this->bc[row * (n_cols - 2) + j].type == BTCSDiffusion::BC_CONSTANT) { A_matrix.insert(offset + j, offset + j) = 1; @@ -218,7 +209,7 @@ void BTCSDiffusion::fillMatrixFromRow(const Eigen::VectorXd &alpha, int n_cols, } } -void BTCSDiffusion::fillVectorFromRow2D(Eigen::Map &c, +void BTCSDiffusion::fillVectorFromRow2D(Eigen::Map &c, const Eigen::VectorXd alpha, int row, double delta, boundary_condition left, boundary_condition right) { @@ -270,16 +261,16 @@ void BTCSDiffusion::simulate(std::vector &c, const std::vector &alpha) { if (this->grid_dim == 1) { assert(c.size() == grid_cells[0]); - Eigen::Map c_in(c.data(), this->grid_cells[0]); + Eigen::Map c_in(c.data(), this->grid_cells[0]); simulate1D(c_in, bc[0], bc[grid_cells[0] + 1], alpha, this->deltas[0], this->grid_cells[0]); } if (this->grid_dim == 2) { assert(c.size() == grid_cells[0] * grid_cells[1]); - Eigen::Map c_in(c.data(), this->grid_cells[1], + Eigen::Map c_in(c.data(), this->grid_cells[1], this->grid_cells[0]); - Eigen::Map alpha_in( + Eigen::Map alpha_in( alpha.data(), this->grid_cells[1], this->grid_cells[0]); simulate2D(c_in, alpha_in); diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 2182cbe..70fe052 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -137,16 +138,21 @@ private: } boundary_condition; typedef Eigen::Triplet T; - void simulate1D(Eigen::Map &c, boundary_condition left, + typedef Eigen::Matrix + DMatrixRowMajor; + typedef Eigen::Matrix + DVectorRowMajor; + + void simulate1D(Eigen::Map &c, boundary_condition left, boundary_condition right, const std::vector &alpha, double dx, int size); - void simulate2D(Eigen::Map &c, - Eigen::Map &alpha); + void simulate2D(Eigen::Map &c, + Eigen::Map &alpha); - inline void fillMatrixFromRow(const Eigen::VectorXd &alpha, int n_cols, int row, - bool left_constant, bool right_constant, - double delta, double time_step); - void fillVectorFromRow2D(Eigen::Map &c, + void fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, int row, + bool left_constant, bool right_constant, double delta, + double time_step); + void fillVectorFromRow2D(Eigen::Map &c, const Eigen::VectorXd alpha, int row, double delta, boundary_condition left, boundary_condition right); void simulate3D(std::vector &c); From 719855288eeca9a29b7ae0b17692773c5c5278ff Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Fri, 11 Feb 2022 13:53:55 +0100 Subject: [PATCH 09/51] Implented second half of ADI-BTCS --- src/BTCSDiffusion.cpp | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 6295bfe..b452a73 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -145,7 +145,7 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, void BTCSDiffusion::simulate2D(Eigen::Map &c, Eigen::Map &alpha) { - DMatrixRowMajor tmp_vector = x_vector; + DMatrixRowMajor tmp_vector; int n_cols = c.cols(); unsigned int size = (this->grid_cells[0] + 2) * (this->grid_cells[1]); @@ -169,12 +169,46 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, solveLES(); + tmp_vector = x_vector; tmp_vector.transposeInPlace(); tmp_vector.conservativeResize(c.rows(), c.cols() + 2); Eigen::Map tmp(tmp_vector.data(), c.rows(), c.cols() + 2); c = tmp_vector.block(0, 1, c.rows(), c.cols()); + c.transposeInPlace(); + + size = (this->grid_cells[0] * (this->grid_cells[1] + 2)); + + A_matrix.resize(size, size); + A_matrix.reserve(Eigen::VectorXi::Constant(size, 3)); + + b_vector.resize(size); + x_vector.resize(size); + + int bottom_offset = bc.size() - (this->grid_cells[0]); + n_cols = c.cols(); + + for (int i = 0; i < c.rows(); i++) { + boundary_condition left = bc[i]; + bool left_constant = left.type == BTCSDiffusion::BC_CONSTANT; + boundary_condition right = bc[bottom_offset + i]; + bool right_constant = right.type == BTCSDiffusion::BC_CONSTANT; + + fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, + deltas[1], this->time_step / 2); + fillVectorFromRow2D(c, alpha.row(i), i, deltas[1], left, right); + } + + solveLES(); + + tmp_vector = x_vector; + tmp_vector.transposeInPlace(); + tmp_vector.conservativeResize(c.rows(), c.cols() + 2); + + c = tmp_vector.block(0, 1, c.rows(), c.cols()); + + c.transposeInPlace(); } void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, From b985707d2c512426e26b9d73b04f6b3b94348e96 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Fri, 11 Feb 2022 14:08:40 +0100 Subject: [PATCH 10/51] Update output of test application --- src/main_2D.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main_2D.cpp b/src/main_2D.cpp index 0893a22..d278425 100644 --- a/src/main_2D.cpp +++ b/src/main_2D.cpp @@ -34,13 +34,18 @@ int main(int argc, char *argv[]) { // loop 100 times // output is currently generated by the method itself - for (int i = 0; i < 1; i++) { + for (int t = 0; t < 1; t++) { diffu.simulate(field, alpha); - cout << "Iteration: " << i << "\n\n"; + cout << "Iteration: " << t << "\n\n"; - for (int j = 0; j < field.size(); j++) { - cout << field[j] << "\n"; + // iterate through rows + for (int i = 0; i < m; i++) { + // iterate through columns + for (int j = 0; j < n; j++) { + cout << left << std::setw(20) << field[i*n + j]; + } + cout << "\n"; } cout << "\n" << endl; From 6ea3bf01821bc8c69ad6ff9135a1f04d0b7e3688 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Fri, 11 Feb 2022 17:37:41 +0100 Subject: [PATCH 11/51] Fix indexing of boundary condition vector --- src/BTCSDiffusion.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index b452a73..507ce21 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -229,10 +229,11 @@ void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, if (right_constant) A_matrix.insert(offset + (n_cols - 2), offset + (n_cols - 2)) = 1; - for (int j = 1 + left_constant; j < n_cols - (1 - right_constant); j++) { + for (int j = 1 + left_constant, k = j - 1; j < n_cols - (1 - right_constant); + j++, k++) { double sx = (alpha[j - 1] * time_step) / (delta * delta); - if (this->bc[row * (n_cols - 2) + j].type == BTCSDiffusion::BC_CONSTANT) { + if (this->bc[row * (n_cols - 2) + k].type == BTCSDiffusion::BC_CONSTANT) { A_matrix.insert(offset + j, offset + j) = 1; continue; } From 08dba0975b7eefcad79c0145ddb5bdeaa77776b0 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Fri, 11 Feb 2022 17:38:39 +0100 Subject: [PATCH 12/51] Refactor loop breakout to same syntax as in fillMatrix --- src/BTCSDiffusion.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 507ce21..dc68093 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -269,22 +269,22 @@ void BTCSDiffusion::fillVectorFromRow2D(Eigen::Map &c, if (tmp_bc.type == BTCSDiffusion::BC_CONSTANT) { b_vector[offset * row + j] = tmp_bc.value; - } else { + continue; + } - double y_values[3]; - y_values[0] = - (row != 0 ? c(row - 1, j - 1) - : getBCFromFlux(tmp_bc, c(row, j - 1), alpha[j - 1])); - y_values[1] = c(row, j - 1); - y_values[2] = (row != nrow - 1 - ? c(row + 1, j - 1) + double y_values[3]; + y_values[0] = + (row != 0 ? c(row - 1, j - 1) + : getBCFromFlux(tmp_bc, c(row, j - 1), alpha[j - 1])); + y_values[1] = c(row, j - 1); + y_values[2] = + (row != nrow - 1 ? c(row + 1, j - 1) : getBCFromFlux(tmp_bc, c(row, j - 1), alpha[j - 1])); - double t0_c = - alpha[j - 1] * - ((y_values[0] - 2 * y_values[1] + y_values[2]) / (delta * delta)); - b_vector[offset * row + j] = -c(row, j - 1) - t0_c; - } + double t0_c = + alpha[j - 1] * + ((y_values[0] - 2 * y_values[1] + y_values[2]) / (delta * delta)); + b_vector[offset * row + j] = -c(row, j - 1) - t0_c; } } From e1a08ea555554a53deba6d041f23475577fe8423 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Mon, 14 Feb 2022 16:46:49 +0100 Subject: [PATCH 13/51] Use Eigen::Matrix for internal BC representation --- src/BTCSDiffusion.cpp | 39 ++++++++++++++++----------------------- src/BTCSDiffusion.hpp | 5 +++-- src/main.cpp | 2 +- src/main_2D.cpp | 10 +++++----- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index dc68093..161df32 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -14,9 +14,9 @@ #include -const int BTCSDiffusion::BC_CONSTANT = 0; -const int BTCSDiffusion::BC_CLOSED = 1; -const int BTCSDiffusion::BC_FLUX = 2; +const int BTCSDiffusion::BC_CLOSED = 0; +const int BTCSDiffusion::BC_FLUX = 1; +const int BTCSDiffusion::BC_CONSTANT = 2; BTCSDiffusion::BTCSDiffusion(unsigned int dim) : grid_dim(dim) { assert(dim <= 3); @@ -65,13 +65,7 @@ void BTCSDiffusion::updateInternals() { deltas[i] = (double)domain_size[i] / grid_cells[i]; } - int cells = 1; - - for (int i = 0; i < grid_dim; i++) { - cells *= (grid_cells[i] + 2); - } - - bc.resize(cells, {BTCSDiffusion::BC_CLOSED, 0}); + bc.resize((grid_dim > 1 ? grid_cells[1] : 1), grid_cells[0]); } void BTCSDiffusion::simulate1D(Eigen::Map &c, @@ -121,9 +115,9 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, i++, j++) { // if current grid cell is considered as constant boundary conditon - if (bc[j].type == BTCSDiffusion::BC_CONSTANT) { + if (bc(1,j).type == BTCSDiffusion::BC_CONSTANT) { A_matrix.insert(i, i) = 1; - b_vector[i] = bc[j].value; + b_vector[i] = bc(1,j).value; continue; } @@ -157,9 +151,9 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, x_vector.resize(size); for (int i = 0; i < c.rows(); i++) { - boundary_condition left = bc[i * n_cols]; + boundary_condition left = bc(i, 0); bool left_constant = left.type == BTCSDiffusion::BC_CONSTANT; - boundary_condition right = bc[((i + 1) * n_cols) - 1]; + boundary_condition right = bc(i, n_cols - 1); bool right_constant = right.type == BTCSDiffusion::BC_CONSTANT; fillMatrixFromRow(alpha.row(i), n_cols, i, left_constant, right_constant, @@ -186,13 +180,12 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, b_vector.resize(size); x_vector.resize(size); - int bottom_offset = bc.size() - (this->grid_cells[0]); n_cols = c.cols(); for (int i = 0; i < c.rows(); i++) { - boundary_condition left = bc[i]; + boundary_condition left = bc(0,i); bool left_constant = left.type == BTCSDiffusion::BC_CONSTANT; - boundary_condition right = bc[bottom_offset + i]; + boundary_condition right = bc(n_cols-1,i); bool right_constant = right.type == BTCSDiffusion::BC_CONSTANT; fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, @@ -233,7 +226,7 @@ void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, j++, k++) { double sx = (alpha[j - 1] * time_step) / (delta * delta); - if (this->bc[row * (n_cols - 2) + k].type == BTCSDiffusion::BC_CONSTANT) { + if (this->bc(row, k).type == BTCSDiffusion::BC_CONSTANT) { A_matrix.insert(offset + j, offset + j) = 1; continue; } @@ -265,7 +258,7 @@ void BTCSDiffusion::fillVectorFromRow2D(Eigen::Map &c, } for (int j = 1; j < offset - 1; j++) { - boundary_condition tmp_bc = this->bc[ncol * row + (j - 1)]; + boundary_condition tmp_bc = this->bc(row, j-1); if (tmp_bc.type == BTCSDiffusion::BC_CONSTANT) { b_vector[offset * row + j] = tmp_bc.value; @@ -297,7 +290,7 @@ void BTCSDiffusion::simulate(std::vector &c, if (this->grid_dim == 1) { assert(c.size() == grid_cells[0]); Eigen::Map c_in(c.data(), this->grid_cells[0]); - simulate1D(c_in, bc[0], bc[grid_cells[0] + 1], alpha, this->deltas[0], + simulate1D(c_in, bc(1,0), bc(1,bc.cols()-1), alpha, this->deltas[0], this->grid_cells[0]); } if (this->grid_dim == 2) { @@ -330,10 +323,10 @@ inline double BTCSDiffusion::getBCFromFlux(boundary_condition bc, return val; } -void BTCSDiffusion::setBoundaryCondition(int index, bctype type, double value) { +void BTCSDiffusion::setBoundaryCondition(int i, int j, bctype type, double value) { - bc[index].type = type; - bc[index].value = value; + bc(i,j).type = type; + bc(i,j).value = value; } inline void BTCSDiffusion::solveLES() { diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 70fe052..2e9b8fd 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -129,7 +129,7 @@ public: * during solving. For flux value refers to a gradient of change for this grid * cell. For closed this value has no effect since a gradient of 0 is used. */ - void setBoundaryCondition(int index, bctype type, double value); + void setBoundaryCondition(int row, int column, bctype type, double value); private: typedef struct boundary_condition { @@ -161,7 +161,8 @@ private: void solveLES(); void updateInternals(); - std::vector bc; + // std::vector bc; + Eigen::Matrix bc; Eigen::SparseMatrix A_matrix; Eigen::VectorXd b_vector; diff --git a/src/main.cpp b/src/main.cpp index 522c5c6..6f03403 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ int main(int argc, char *argv[]) { diffu.setXDimensions(1, n); // set the boundary condition for the left ghost cell to dirichlet - diffu.setBoundaryCondition(0, BTCSDiffusion::BC_CONSTANT, + diffu.setBoundaryCondition(1, 0, BTCSDiffusion::BC_CONSTANT, 5. * std::pow(10, -6)); // set timestep for simulation to 1 second diff --git a/src/main_2D.cpp b/src/main_2D.cpp index d278425..49878ab 100644 --- a/src/main_2D.cpp +++ b/src/main_2D.cpp @@ -1,5 +1,5 @@ #include "BTCSDiffusion.hpp" // for BTCSDiffusion, BTCSDiffusion::BC_DIRICHLET -#include // for copy, max +#include // for copy, max #include #include // for std #include // for vector @@ -14,8 +14,8 @@ int main(int argc, char *argv[]) { int m = 5; // create input + diffusion coefficients for each grid cell - std::vector alpha(n*m, 1 * pow(10, -1)); - std::vector field(n*m, 1 * std::pow(10, -6)); + std::vector alpha(n * m, 1 * pow(10, -1)); + std::vector field(n * m, 1 * std::pow(10, -6)); // create instance of diffusion module BTCSDiffusion diffu(dim); @@ -24,7 +24,7 @@ int main(int argc, char *argv[]) { diffu.setYDimensions(1, m); // set the boundary condition for the left ghost cell to dirichlet - diffu.setBoundaryCondition(0, BTCSDiffusion::BC_CONSTANT, + diffu.setBoundaryCondition(2, 2, BTCSDiffusion::BC_CONSTANT, 5. * std::pow(10, -6)); // set timestep for simulation to 1 second @@ -43,7 +43,7 @@ int main(int argc, char *argv[]) { for (int i = 0; i < m; i++) { // iterate through columns for (int j = 0; j < n; j++) { - cout << left << std::setw(20) << field[i*n + j]; + cout << left << std::setw(20) << field[i * n + j]; } cout << "\n"; } From 6661a8cbd4b821004e33d5dc8848c5fc3cb12ea2 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 17 Feb 2022 09:19:09 +0100 Subject: [PATCH 14/51] Refactoring function name + signature - domain_size is now double value - fillVectorFromRow2D renamed to fillVectorFromRowADI --- src/BTCSDiffusion.cpp | 12 ++++++------ src/BTCSDiffusion.hpp | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 161df32..fd1b5a7 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -26,7 +26,7 @@ BTCSDiffusion::BTCSDiffusion(unsigned int dim) : grid_dim(dim) { deltas.resize(dim, 1); } -void BTCSDiffusion::setXDimensions(unsigned int domain_size, +void BTCSDiffusion::setXDimensions(double domain_size, unsigned int n_grid_cells) { assert(this->grid_dim > 0); this->domain_size[0] = domain_size; @@ -35,7 +35,7 @@ void BTCSDiffusion::setXDimensions(unsigned int domain_size, updateInternals(); } -void BTCSDiffusion::setYDimensions(unsigned int domain_size, +void BTCSDiffusion::setYDimensions(double domain_size, unsigned int n_grid_cells) { assert(this->grid_dim > 1); this->domain_size[1] = domain_size; @@ -44,7 +44,7 @@ void BTCSDiffusion::setYDimensions(unsigned int domain_size, updateInternals(); } -void BTCSDiffusion::setZDimensions(unsigned int domain_size, +void BTCSDiffusion::setZDimensions(double domain_size, unsigned int n_grid_cells) { assert(this->grid_dim > 2); this->domain_size[2] = domain_size; @@ -158,7 +158,7 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, fillMatrixFromRow(alpha.row(i), n_cols, i, left_constant, right_constant, deltas[0], this->time_step / 2); - fillVectorFromRow2D(c, alpha.row(i), i, deltas[0], left, right); + fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right); } solveLES(); @@ -190,7 +190,7 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, deltas[1], this->time_step / 2); - fillVectorFromRow2D(c, alpha.row(i), i, deltas[1], left, right); + fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right); } solveLES(); @@ -237,7 +237,7 @@ void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, } } -void BTCSDiffusion::fillVectorFromRow2D(Eigen::Map &c, +void BTCSDiffusion::fillVectorFromRowADI(Eigen::Map &c, const Eigen::VectorXd alpha, int row, double delta, boundary_condition left, boundary_condition right) { diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 2e9b8fd..ebfda26 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -51,7 +51,7 @@ public: * @param n_grid_cells Number of grid cells in x direction the domain is * divided to. */ - void setXDimensions(unsigned int domain_size, unsigned int n_grid_cells); + void setXDimensions(double domain_size, unsigned int n_grid_cells); /*! * Define the grid in y direction. @@ -62,7 +62,7 @@ public: * @param n_grid_cells Number of grid cells in y direction the domain is * divided to. */ - void setYDimensions(unsigned int domain_size, unsigned int n_grid_cells); + void setYDimensions(double domain_size, unsigned int n_grid_cells); /*! * Define the grid in z direction. @@ -73,7 +73,7 @@ public: * @param n_grid_cells Number of grid cells in z direction the domain is * divided to. */ - void setZDimensions(unsigned int domain_size, unsigned int n_grid_cells); + void setZDimensions(double domain_size, unsigned int n_grid_cells); /*! * Returns the number of grid cells in x direction. @@ -152,7 +152,7 @@ private: void fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, int row, bool left_constant, bool right_constant, double delta, double time_step); - void fillVectorFromRow2D(Eigen::Map &c, + void fillVectorFromRowADI(Eigen::Map &c, const Eigen::VectorXd alpha, int row, double delta, boundary_condition left, boundary_condition right); void simulate3D(std::vector &c); @@ -172,7 +172,7 @@ private: int grid_dim; std::vector grid_cells; - std::vector domain_size; + std::vector domain_size; std::vector deltas; }; From f296bd93a1a47f96164a411cb92a88761460038d Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 17 Feb 2022 09:26:42 +0100 Subject: [PATCH 15/51] Increase iterations to 10 --- src/main_2D.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main_2D.cpp b/src/main_2D.cpp index 49878ab..fbcfaf5 100644 --- a/src/main_2D.cpp +++ b/src/main_2D.cpp @@ -32,9 +32,7 @@ int main(int argc, char *argv[]) { cout << setprecision(12); - // loop 100 times - // output is currently generated by the method itself - for (int t = 0; t < 1; t++) { + for (int t = 0; t < 10; t++) { diffu.simulate(field, alpha); cout << "Iteration: " << t << "\n\n"; From 79f0ded6b060fb8d72afc5507b885b9ec4e724b1 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 17 Feb 2022 10:46:16 +0100 Subject: [PATCH 16/51] Added org document of filling scheme --- doc/.gitignore | 2 + doc/ADI_scheme.org | 103 +++++++++++++++++++++++++++++++++++++++++++++ doc/grid.png | Bin 0 -> 154501 bytes 3 files changed, 105 insertions(+) create mode 100644 doc/.gitignore create mode 100644 doc/ADI_scheme.org create mode 100644 doc/grid.png diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..1d18d0b --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,2 @@ +/doc/*.pdf +/doc/*.tex diff --git a/doc/ADI_scheme.org b/doc/ADI_scheme.org new file mode 100644 index 0000000..9583783 --- /dev/null +++ b/doc/ADI_scheme.org @@ -0,0 +1,103 @@ +#+TITLE: Adi Scheme + +* Input + +- =c= $\rightarrow c$ + - containing current concentrations at each grid cell for species + - size: $N \times M$ + - row-major +- =alpha= $\rightarrow \alpha$ + - diffusion coefficient for both directions (x and y) + - size: $N \times M$ + - row-major +- =boundary_condition= $\rightarrow bc$ + - Defines closed or constant boundary condition for each grid cell + - size: $N \times M$ + - row-major + +* Internals + +- =A_matrix= $\rightarrow A$ + - coefficient matrix for linear equation system implemented as sparse matrix + - size: $((N+2)\cdot M) \times ((N+2)\cdot M)$ (including ghost zones in x direction) + - column-major (not relevant) + +- =b_vector= $\rightarrow b$ + - right hand side of the linear equation system + - size: $(N+2) \cdot M$ + - column-major (not relevant) +- =x_vector= $\rightarrow x$ + - solutions of the linear equation system + - size: $(N+2) \cdot M$ + - column-major (not relevant) + +* Calculation for $\frac{1}{2}$ timestep + +** Symbolic addressing of grid cells +[[./grid.png]] + +** Filling of matrix $A$ + +- row-wise iterating with $i$ over =c= and =\alpha= matrix respectively +- addressing each element of a row with $j$ +- matrix $A$ also containing $+2$ ghost nodes for each row of input matrix $\alpha$ + - $\rightarrow offset = N+2$ + - addressing each object $(i,j)$ in matrix $A$ with $(offset \cdot i + j, offset \cdot i + j)$ + +*** Rules + +$s_x(i,j) = \frac{\alpha(i,j)*\frac{t}{2}}{\Delta x^2}$ where $x$ defining the domain size in x direction. + +For the sake of simplicity we assume that each row of the $A$ matrix is addressed correctly with the given offset. + +**** Ghost nodes + +$A(i,-1) = 1$ + +$A(i,N) = 1$ + +**** Inlet + +$A(i,j) = \begin{cases} +1 & \text{if } bc(i,j) = \text{constant} \\ +-1-2*s_x(i,j) & \text{else} +\end{cases}$ + +$A(i,j\pm 1) = \begin{cases} +0 & \text{if } bc(i,j) = \text{constant} \\ +s_x(i,j) & \text{else} +\end{cases}$ + +** Filling of vector $b$ + +- each elements assign a concrete value to the according value of the row of matrix $A$ +- Adressing would look like this: $(i,j) = b(i \cdot (N+2) + j)$ + - $\rightarrow$ for simplicity we will write $b(i,j)$ + + + + +*** Rules + +**** Ghost nodes + +$b(i,-1) = \begin{cases} +0 & \text{if } bc(i,0) = \text{constant} \\ +c(i,0) & \text{else} +\end{cases}$ + +$b(i,N) = \begin{cases} +0 & \text{if } bc(i,N-1) = \text{constant} \\ +c(i,N-1) & \text{else} +\end{cases}$ + +*** Inlet + +$p(i,j) = \alpha(i,j)\frac{c(i-1,j) - 2\cdot c(i,j) + c(i+1,j)}{\Delta x^2}$[fn:1] + +$b(i,j) = \begin{cases} +bc(i,j).\text{value} & \text{if } bc(i,N-1) = \text{constant} \\ +-c(i,j)-p(i,j) & \text{else} +\end{cases}$ + +[fn:1] $p$ is called =t0_c= inside code diff --git a/doc/grid.png b/doc/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..86940a46bd3e708c1b90d3be915eda895d05a422 GIT binary patch literal 154501 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 z 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 literal 0 HcmV?d00001 From b3e0f7987b9d983cc796e572e7d4b3a6365b02c7 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 17 Feb 2022 10:47:40 +0100 Subject: [PATCH 17/51] Update doc/.gitignore --- doc/.gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/.gitignore b/doc/.gitignore index 1d18d0b..b51a70d 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,2 +1,2 @@ -/doc/*.pdf -/doc/*.tex +*.pdf +*.tex From 0dad4f93fd6cc2f43bfb6fe0b6bef409fd6b93f9 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 17 Feb 2022 16:43:04 +0100 Subject: [PATCH 18/51] Change loop index variable --- src/BTCSDiffusion.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index fd1b5a7..af5907d 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -257,27 +257,26 @@ void BTCSDiffusion::fillVectorFromRowADI(Eigen::Map &c, getBCFromFlux(right, c(row, ncol - 1), alpha[ncol - 1]); } - for (int j = 1; j < offset - 1; j++) { - boundary_condition tmp_bc = this->bc(row, j-1); + for (int j = 0; j < ncol; j++) { + boundary_condition tmp_bc = this->bc(row, j); if (tmp_bc.type == BTCSDiffusion::BC_CONSTANT) { - b_vector[offset * row + j] = tmp_bc.value; + b_vector[offset * row + (j+1)] = tmp_bc.value; continue; } double y_values[3]; y_values[0] = - (row != 0 ? c(row - 1, j - 1) - : getBCFromFlux(tmp_bc, c(row, j - 1), alpha[j - 1])); - y_values[1] = c(row, j - 1); + (row != 0 ? c(row - 1, j) : getBCFromFlux(tmp_bc, c(row, j), alpha[j])); + y_values[1] = c(row, j); y_values[2] = - (row != nrow - 1 ? c(row + 1, j - 1) - : getBCFromFlux(tmp_bc, c(row, j - 1), alpha[j - 1])); + (row != nrow - 1 ? c(row + 1, j) + : getBCFromFlux(tmp_bc, c(row, j), alpha[j])); double t0_c = - alpha[j - 1] * + alpha[j] * ((y_values[0] - 2 * y_values[1] + y_values[2]) / (delta * delta)); - b_vector[offset * row + j] = -c(row, j - 1) - t0_c; + b_vector[offset * row + (j + 1)] = -c(row, j) - t0_c; } } From 78ef8c2833e9e7c0a3852fb86c3a5130e81fb647 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 17 Feb 2022 17:02:25 +0100 Subject: [PATCH 19/51] Update calculation of t0_c - Added time dependency by multiplying spacial context with current time step --- doc/ADI_scheme.org | 2 +- src/BTCSDiffusion.cpp | 9 +++++---- src/BTCSDiffusion.hpp | 5 +++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/ADI_scheme.org b/doc/ADI_scheme.org index 9583783..b719c4a 100644 --- a/doc/ADI_scheme.org +++ b/doc/ADI_scheme.org @@ -93,7 +93,7 @@ c(i,N-1) & \text{else} *** Inlet -$p(i,j) = \alpha(i,j)\frac{c(i-1,j) - 2\cdot c(i,j) + c(i+1,j)}{\Delta x^2}$[fn:1] +$p(i,j) = \frac{\Delta t}{2}\alpha(i,j)\frac{c(i-1,j) - 2\cdot c(i,j) + c(i+1,j)}{\Delta x^2}$[fn:1] $b(i,j) = \begin{cases} bc(i,j).\text{value} & \text{if } bc(i,N-1) = \text{constant} \\ diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index af5907d..4f31c42 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -139,6 +139,7 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, void BTCSDiffusion::simulate2D(Eigen::Map &c, Eigen::Map &alpha) { + double local_dt = this->time_step/ 2.; DMatrixRowMajor tmp_vector; int n_cols = c.cols(); @@ -158,7 +159,7 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, fillMatrixFromRow(alpha.row(i), n_cols, i, left_constant, right_constant, deltas[0], this->time_step / 2); - fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right); + fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right, local_dt); } solveLES(); @@ -190,7 +191,7 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, deltas[1], this->time_step / 2); - fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right); + fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right, local_dt); } solveLES(); @@ -274,9 +275,9 @@ void BTCSDiffusion::fillVectorFromRowADI(Eigen::Map &c, : getBCFromFlux(tmp_bc, c(row, j), alpha[j])); double t0_c = - alpha[j] * + time_step * alpha[j] * ((y_values[0] - 2 * y_values[1] + y_values[2]) / (delta * delta)); - b_vector[offset * row + (j + 1)] = -c(row, j) - t0_c; + b_vector[offset * row + (j + 1)] = -c(row, j) - (t0_c); } } diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index ebfda26..8d12660 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -153,8 +153,9 @@ private: bool left_constant, bool right_constant, double delta, double time_step); void fillVectorFromRowADI(Eigen::Map &c, - const Eigen::VectorXd alpha, int row, double delta, - boundary_condition left, boundary_condition right); + const Eigen::VectorXd alpha, int row, double delta, + boundary_condition left, boundary_condition right, + double time_step); void simulate3D(std::vector &c); inline double getBCFromFlux(boundary_condition bc, double nearest_value, double neighbor_alpha); From 5f4d81268102f886f3931e1bd4c6ac3da9e8ce81 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 17 Feb 2022 17:39:23 +0100 Subject: [PATCH 20/51] Added CI support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit 8a0d9cea8121f62ea518a9ab2c48ffc334104ecd Author: Max Lübke Date: Tue Feb 15 20:31:32 2022 +0100 Added tidy analyzer options commit bd59a32420acb282ceba80c13e1f727d1ae3a767 Author: Max Lübke Date: Tue Feb 15 11:09:33 2022 +0100 Update .gitlab-ci.yml file commit ccfcec4f9c0c43341f3b73f2da8ac83ee67e35dc Author: Max Lübke Date: Tue Feb 15 10:55:08 2022 +0100 Update .gitlab-ci.yml file commit c2da2361e0e152a8fd51f0e89ab4fb0afbad57a1 Author: Max Lübke Date: Tue Feb 15 10:47:40 2022 +0100 Update .gitlab-ci.yml file commit 6c10f3b42ae3479f747aab012f7411d48493c426 Author: Max Lübke Date: Tue Feb 15 10:47:16 2022 +0100 Update .gitlab-ci.yml file commit 8f96ccc33556d97e5d37fd448b3f12e024777274 Author: Max Lübke Date: Tue Feb 15 10:46:25 2022 +0100 Update .gitlab-ci.yml file commit afdb0447625d35d6ca989744e94a44f90392d1c7 Author: Max Lübke Date: Tue Feb 15 10:44:56 2022 +0100 Update .gitlab-ci.yml file --- .gitlab-ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..5b38c99 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,22 @@ +image: sobc/gitlab-ci + +stages: + - build + - test + +before_script: + - apt-get update && apt-get install -y libeigen3-dev + +build: + stage: build + script: + - mkdir build && cd build + - cmake .. + - make + +lint: + stage: test + script: + - mkdir build && cd build + - cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-checks=cppcoreguidelines-*,clang-analyzer-*,performance-*,readability-*, modernize-*" .. + - make From d2866c271f3e7604d5530a04a437ddb92b0a9e76 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Thu, 17 Feb 2022 17:43:27 +0100 Subject: [PATCH 21/51] Fix wrong function signature in implementation --- src/BTCSDiffusion.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 4f31c42..a2b414c 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -239,9 +239,10 @@ void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, } void BTCSDiffusion::fillVectorFromRowADI(Eigen::Map &c, - const Eigen::VectorXd alpha, int row, - double delta, boundary_condition left, - boundary_condition right) { + const Eigen::VectorXd alpha, int row, + double delta, boundary_condition left, + boundary_condition right, + double time_step) { int ncol = c.cols(); int nrow = c.rows(); From d2b0ad8d604669da288dbb409882d4fc88794bab Mon Sep 17 00:00:00 2001 From: Marco De Lucia Date: Fri, 18 Feb 2022 10:12:58 +0100 Subject: [PATCH 22/51] Some orgmode tweaks in doc --- doc/ADI_scheme.org | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/ADI_scheme.org b/doc/ADI_scheme.org index 9583783..dc69945 100644 --- a/doc/ADI_scheme.org +++ b/doc/ADI_scheme.org @@ -1,4 +1,7 @@ -#+TITLE: Adi Scheme +#+TITLE: Adi 2D Scheme +#+LaTeX_CLASS_OPTIONS: [a4paper,10pt] +#+LATEX_HEADER: \usepackage{fullpage} +#+OPTIONS: toc:nil * Input From d7e240c6a8ef25e0b4e724ccf08c7882391d1a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Mon, 28 Feb 2022 14:10:53 +0100 Subject: [PATCH 23/51] Refactor simulate function signature --- src/BTCSDiffusion.cpp | 167 ++++++++++++++++++++------------------ src/BTCSDiffusion.hpp | 80 +++++++----------- src/BoundaryCondition.hpp | 33 ++++++++ src/main.cpp | 12 ++- src/main_2D.cpp | 12 ++- 5 files changed, 164 insertions(+), 140 deletions(-) create mode 100644 src/BoundaryCondition.hpp diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index a2b414c..274dd05 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -14,11 +14,7 @@ #include -const int BTCSDiffusion::BC_CLOSED = 0; -const int BTCSDiffusion::BC_FLUX = 1; -const int BTCSDiffusion::BC_CONSTANT = 2; - -BTCSDiffusion::BTCSDiffusion(unsigned int dim) : grid_dim(dim) { +Diffusion::BTCSDiffusion::BTCSDiffusion(unsigned int dim) : grid_dim(dim) { assert(dim <= 3); grid_cells.resize(dim, 1); @@ -26,8 +22,8 @@ BTCSDiffusion::BTCSDiffusion(unsigned int dim) : grid_dim(dim) { deltas.resize(dim, 1); } -void BTCSDiffusion::setXDimensions(double domain_size, - unsigned int n_grid_cells) { +void Diffusion::BTCSDiffusion::setXDimensions(double domain_size, + unsigned int n_grid_cells) { assert(this->grid_dim > 0); this->domain_size[0] = domain_size; this->grid_cells[0] = n_grid_cells; @@ -35,8 +31,8 @@ void BTCSDiffusion::setXDimensions(double domain_size, updateInternals(); } -void BTCSDiffusion::setYDimensions(double domain_size, - unsigned int n_grid_cells) { +void Diffusion::BTCSDiffusion::setYDimensions(double domain_size, + unsigned int n_grid_cells) { assert(this->grid_dim > 1); this->domain_size[1] = domain_size; this->grid_cells[1] = n_grid_cells; @@ -44,8 +40,8 @@ void BTCSDiffusion::setYDimensions(double domain_size, updateInternals(); } -void BTCSDiffusion::setZDimensions(double domain_size, - unsigned int n_grid_cells) { +void Diffusion::BTCSDiffusion::setZDimensions(double domain_size, + unsigned int n_grid_cells) { assert(this->grid_dim > 2); this->domain_size[2] = domain_size; this->grid_cells[2] = n_grid_cells; @@ -53,29 +49,38 @@ void BTCSDiffusion::setZDimensions(double domain_size, updateInternals(); } -unsigned int BTCSDiffusion::getXGridCellsN() { return this->grid_cells[0]; } -unsigned int BTCSDiffusion::getYGridCellsN() { return this->grid_cells[1]; } -unsigned int BTCSDiffusion::getZGridCellsN() { return this->grid_cells[2]; } -unsigned int BTCSDiffusion::getXDomainSize() { return this->domain_size[0]; } -unsigned int BTCSDiffusion::getYDomainSize() { return this->domain_size[1]; } -unsigned int BTCSDiffusion::getZDomainSize() { return this->domain_size[2]; } +unsigned int Diffusion::BTCSDiffusion::getXGridCellsN() { + return this->grid_cells[0]; +} +unsigned int Diffusion::BTCSDiffusion::getYGridCellsN() { + return this->grid_cells[1]; +} +unsigned int Diffusion::BTCSDiffusion::getZGridCellsN() { + return this->grid_cells[2]; +} +unsigned int Diffusion::BTCSDiffusion::getXDomainSize() { + return this->domain_size[0]; +} +unsigned int Diffusion::BTCSDiffusion::getYDomainSize() { + return this->domain_size[1]; +} +unsigned int Diffusion::BTCSDiffusion::getZDomainSize() { + return this->domain_size[2]; +} -void BTCSDiffusion::updateInternals() { +void Diffusion::BTCSDiffusion::updateInternals() { for (int i = 0; i < grid_dim; i++) { deltas[i] = (double)domain_size[i] / grid_cells[i]; } - - bc.resize((grid_dim > 1 ? grid_cells[1] : 1), grid_cells[0]); } -void BTCSDiffusion::simulate1D(Eigen::Map &c, - boundary_condition left, - boundary_condition right, - const std::vector &alpha, double dx, - int size) { +void Diffusion::BTCSDiffusion::simulate1D( + Eigen::Map &c, Diffusion::boundary_condition left, + Diffusion::boundary_condition right, Eigen::Map &bc, + Eigen::Map &alpha, double dx, int size) { - bool left_is_constant = (left.type == BTCSDiffusion::BC_CONSTANT); - bool right_is_constant = (right.type == BTCSDiffusion::BC_CONSTANT); + bool left_is_constant = (left.type == Diffusion::BC_CONSTANT); + bool right_is_constant = (right.type == Diffusion::BC_CONSTANT); // The sizes for matrix and vectors of the equation system is defined by the // actual size of the input vector and if the system is (partially) closed. @@ -115,9 +120,9 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, i++, j++) { // if current grid cell is considered as constant boundary conditon - if (bc(1,j).type == BTCSDiffusion::BC_CONSTANT) { + if (bc[j].type == Diffusion::BC_CONSTANT) { A_matrix.insert(i, i) = 1; - b_vector[i] = bc(1,j).value; + b_vector[i] = bc[j].value; continue; } @@ -136,10 +141,11 @@ void BTCSDiffusion::simulate1D(Eigen::Map &c, c = x_vector.segment(!left_is_constant, c.size()); } -void BTCSDiffusion::simulate2D(Eigen::Map &c, - Eigen::Map &alpha) { +void Diffusion::BTCSDiffusion::simulate2D( + Eigen::Map &c, Eigen::Map &alpha, + Eigen::Map &bc) { - double local_dt = this->time_step/ 2.; + double local_dt = this->time_step / 2.; DMatrixRowMajor tmp_vector; int n_cols = c.cols(); @@ -153,13 +159,13 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, for (int i = 0; i < c.rows(); i++) { boundary_condition left = bc(i, 0); - bool left_constant = left.type == BTCSDiffusion::BC_CONSTANT; + bool left_constant = left.type == Diffusion::BC_CONSTANT; boundary_condition right = bc(i, n_cols - 1); - bool right_constant = right.type == BTCSDiffusion::BC_CONSTANT; + bool right_constant = right.type == Diffusion::BC_CONSTANT; fillMatrixFromRow(alpha.row(i), n_cols, i, left_constant, right_constant, - deltas[0], this->time_step / 2); - fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right, local_dt); + deltas[0], this->time_step / 2, bc.row(i)); + fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right, local_dt, bc.row(i)); } solveLES(); @@ -184,14 +190,14 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, n_cols = c.cols(); for (int i = 0; i < c.rows(); i++) { - boundary_condition left = bc(0,i); - bool left_constant = left.type == BTCSDiffusion::BC_CONSTANT; - boundary_condition right = bc(n_cols-1,i); - bool right_constant = right.type == BTCSDiffusion::BC_CONSTANT; + boundary_condition left = bc(0, i); + bool left_constant = left.type == Diffusion::BC_CONSTANT; + boundary_condition right = bc(n_cols - 1, i); + bool right_constant = right.type == Diffusion::BC_CONSTANT; fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, - deltas[1], this->time_step / 2); - fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right, local_dt); + deltas[1], this->time_step / 2, bc.col(i)); + fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right, local_dt, bc.col(i)); } solveLES(); @@ -205,10 +211,12 @@ void BTCSDiffusion::simulate2D(Eigen::Map &c, c.transposeInPlace(); } -void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, - int row, bool left_constant, - bool right_constant, double delta, - double time_step) { +void Diffusion::BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, + int n_cols, int row, + bool left_constant, + bool right_constant, + double delta, double time_step, + const BCVectorRowMajor &bc) { n_cols += 2; int offset = n_cols * row; @@ -227,7 +235,7 @@ void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, j++, k++) { double sx = (alpha[j - 1] * time_step) / (delta * delta); - if (this->bc(row, k).type == BTCSDiffusion::BC_CONSTANT) { + if (bc[k].type == Diffusion::BC_CONSTANT) { A_matrix.insert(offset + j, offset + j) = 1; continue; } @@ -238,32 +246,31 @@ void BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, } } -void BTCSDiffusion::fillVectorFromRowADI(Eigen::Map &c, - const Eigen::VectorXd alpha, int row, - double delta, boundary_condition left, - boundary_condition right, - double time_step) { +void Diffusion::BTCSDiffusion::fillVectorFromRowADI( + Eigen::Map &c, const Eigen::VectorXd alpha, int row, + double delta, boundary_condition left, boundary_condition right, + double time_step, const BCVectorRowMajor &bc) { int ncol = c.cols(); int nrow = c.rows(); int offset = ncol + 2; - if (left.type != BTCSDiffusion::BC_CONSTANT) { + if (left.type != Diffusion::BC_CONSTANT) { // this is not correct currently.We will fix this when we are able to define // FLUX boundary conditions b_vector[offset * row] = getBCFromFlux(left, c(row, 0), alpha[0]); } - if (right.type != BTCSDiffusion::BC_CONSTANT) { + if (right.type != Diffusion::BC_CONSTANT) { b_vector[offset * row + (offset - 1)] = getBCFromFlux(right, c(row, ncol - 1), alpha[ncol - 1]); } for (int j = 0; j < ncol; j++) { - boundary_condition tmp_bc = this->bc(row, j); + boundary_condition tmp_bc = bc[j]; - if (tmp_bc.type == BTCSDiffusion::BC_CONSTANT) { - b_vector[offset * row + (j+1)] = tmp_bc.value; + if (tmp_bc.type == Diffusion::BC_CONSTANT) { + b_vector[offset * row + (j + 1)] = tmp_bc.value; continue; } @@ -282,39 +289,43 @@ void BTCSDiffusion::fillVectorFromRowADI(Eigen::Map &c, } } -void BTCSDiffusion::setTimestep(double time_step) { +void Diffusion::BTCSDiffusion::setTimestep(double time_step) { this->time_step = time_step; } -void BTCSDiffusion::simulate(std::vector &c, - const std::vector &alpha) { +void Diffusion::BTCSDiffusion::simulate(double *c, double *alpha, + Diffusion::boundary_condition *bc) { if (this->grid_dim == 1) { - assert(c.size() == grid_cells[0]); - Eigen::Map c_in(c.data(), this->grid_cells[0]); - simulate1D(c_in, bc(1,0), bc(1,bc.cols()-1), alpha, this->deltas[0], - this->grid_cells[0]); + Eigen::Map c_in(c, this->grid_cells[0]); + Eigen::Map alpha_in(alpha, this->grid_cells[0]); + Eigen::Map bc_in(bc, this->grid_cells[0]); + + simulate1D(c_in, bc[0], bc[this->grid_cells[0] - 1], bc_in, alpha_in, + this->deltas[0], this->grid_cells[0]); } if (this->grid_dim == 2) { - assert(c.size() == grid_cells[0] * grid_cells[1]); - Eigen::Map c_in(c.data(), this->grid_cells[1], + Eigen::Map c_in(c, this->grid_cells[1], this->grid_cells[0]); - Eigen::Map alpha_in( - alpha.data(), this->grid_cells[1], this->grid_cells[0]); + Eigen::Map alpha_in(alpha, this->grid_cells[1], + this->grid_cells[0]); - simulate2D(c_in, alpha_in); + Eigen::Map bc_in(bc, this->grid_cells[1], + this->grid_cells[0]); + + simulate2D(c_in, alpha_in, bc_in); } } -inline double BTCSDiffusion::getBCFromFlux(boundary_condition bc, - double neighbor_c, - double neighbor_alpha) { +inline double Diffusion::BTCSDiffusion::getBCFromFlux(boundary_condition bc, + double neighbor_c, + double neighbor_alpha) { double val; - if (bc.type == BTCSDiffusion::BC_CLOSED) { + if (bc.type == Diffusion::BC_CLOSED) { val = neighbor_c; - } else if (bc.type == BTCSDiffusion::BC_FLUX) { + } else if (bc.type == Diffusion::BC_FLUX) { // TODO // val = bc[index].value; } else { @@ -324,13 +335,7 @@ inline double BTCSDiffusion::getBCFromFlux(boundary_condition bc, return val; } -void BTCSDiffusion::setBoundaryCondition(int i, int j, bctype type, double value) { - - bc(i,j).type = type; - bc(i,j).value = value; -} - -inline void BTCSDiffusion::solveLES() { +inline void Diffusion::BTCSDiffusion::solveLES() { // start to solve Eigen::SparseLU, Eigen::COLAMDOrdering> solver; diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 8d12660..d8c7cf7 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -1,6 +1,8 @@ #ifndef BTCSDIFFUSION_H_ #define BTCSDIFFUSION_H_ +#include "BoundaryCondition.hpp" + #include #include #include @@ -9,11 +11,7 @@ #include #include -/*! - * Defines both types of boundary condition as a datatype. - */ -typedef int bctype; - +namespace Diffusion { /*! * Class implementing a solution for a 1/2/3D diffusion equation using backward * euler. @@ -21,21 +19,6 @@ typedef int bctype; class BTCSDiffusion { public: - /*! - * Defines a constant/Dirichlet boundary condition. - */ - static const int BC_CONSTANT; - - /*! - * Defines a closed/Neumann boundary condition. - */ - static const int BC_CLOSED; - - /*! - * Defines a flux/Cauchy boundary condition. - */ - static const int BC_FLUX; - /*! * Creates a diffusion module. * @@ -108,7 +91,7 @@ public: * continious memory (row major). * @param alpha Vector of diffusion coefficients for each grid element. */ - void simulate(std::vector &c, const std::vector &alpha); + void simulate(double *c, double *alpha, Diffusion::boundary_condition *bc); /*! * Set the timestep of the simulation @@ -117,53 +100,46 @@ public: */ void setTimestep(double time_step); - /*! - * Set the boundary condition of the given grid. This is done by defining an - * index (exact order still to be determined), the type of the boundary - * condition and the according value. - * - * @param index Index of the grid cell the boundary condition is applied to. - * @param type Type of the boundary condition. Must be constant, closed or - * flux. - * @param value For constant boundary conditions this value is set - * during solving. For flux value refers to a gradient of change for this grid - * cell. For closed this value has no effect since a gradient of 0 is used. - */ - void setBoundaryCondition(int row, int column, bctype type, double value); - private: - typedef struct boundary_condition { - bctype type; - double value; - } boundary_condition; - typedef Eigen::Triplet T; - typedef Eigen::Matrix DMatrixRowMajor; typedef Eigen::Matrix DVectorRowMajor; + typedef Eigen::Matrix + BCMatrixRowMajor; + typedef Eigen::Matrix + BCVectorRowMajor; - void simulate1D(Eigen::Map &c, boundary_condition left, - boundary_condition right, const std::vector &alpha, - double dx, int size); + void simulate1D(Eigen::Map &c, + Diffusion::boundary_condition left, + Diffusion::boundary_condition right, + Eigen::Map &bc, + Eigen::Map &alpha, double dx, + int size); void simulate2D(Eigen::Map &c, - Eigen::Map &alpha); + Eigen::Map &alpha, + Eigen::Map &bc); void fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, int row, bool left_constant, bool right_constant, double delta, - double time_step); + double time_step, const BCVectorRowMajor &bc); void fillVectorFromRowADI(Eigen::Map &c, const Eigen::VectorXd alpha, int row, double delta, - boundary_condition left, boundary_condition right, - double time_step); + Diffusion::boundary_condition left, + Diffusion::boundary_condition right, + double time_step, const BCVectorRowMajor &bc); void simulate3D(std::vector &c); - inline double getBCFromFlux(boundary_condition bc, double nearest_value, - double neighbor_alpha); + inline double getBCFromFlux(Diffusion::boundary_condition bc, + double nearest_value, double neighbor_alpha); void solveLES(); void updateInternals(); // std::vector bc; - Eigen::Matrix bc; + // Eigen::Matrix + // bc; Eigen::SparseMatrix A_matrix; Eigen::VectorXd b_vector; @@ -176,5 +152,5 @@ private: std::vector domain_size; std::vector deltas; }; - +} // namespace Diffusion #endif // BTCSDIFFUSION_H_ diff --git a/src/BoundaryCondition.hpp b/src/BoundaryCondition.hpp new file mode 100644 index 0000000..e47482d --- /dev/null +++ b/src/BoundaryCondition.hpp @@ -0,0 +1,33 @@ +#ifndef BOUNDARYCONDITION_H_ +#define BOUNDARYCONDITION_H_ + +namespace Diffusion { + +/*! + * Defines both types of boundary condition as a datatype. + */ +typedef int bctype; + +/*! + * Defines a closed/Neumann boundary condition. + */ +static const bctype BC_CLOSED = 0; + +/*! + * Defines a flux/Cauchy boundary condition. + */ +static const bctype BC_FLUX = 1; + +/*! + * Defines a constant/Dirichlet boundary condition. + */ +static const bctype BC_CONSTANT = 2; + +typedef struct boundary_condition { + bctype type; + double value; +} boundary_condition; + +} // namespace Diffusion + +#endif // BOUNDARYCONDITION_H_ diff --git a/src/main.cpp b/src/main.cpp index 6f03403..6432527 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,14 @@ #include "BTCSDiffusion.hpp" // for BTCSDiffusion, BTCSDiffusion::BC_DIRICHLET +#include "BoundaryCondition.hpp" #include // for copy, max +#include #include #include // for std #include // for vector using namespace std; +using namespace Diffusion; + int main(int argc, char *argv[]) { // dimension of grid @@ -15,6 +19,7 @@ int main(int argc, char *argv[]) { // create input + diffusion coefficients for each grid cell std::vector alpha(n, 1 * pow(10, -1)); std::vector field(n, 1 * std::pow(10, -6)); + std::vector bc(n, {0,0}); // create instance of diffusion module BTCSDiffusion diffu(dim); @@ -22,8 +27,9 @@ int main(int argc, char *argv[]) { diffu.setXDimensions(1, n); // set the boundary condition for the left ghost cell to dirichlet - diffu.setBoundaryCondition(1, 0, BTCSDiffusion::BC_CONSTANT, - 5. * std::pow(10, -6)); + bc[0] = {Diffusion::BC_CONSTANT, 5*std::pow(10,-6)}; + // diffu.setBoundaryCondition(1, 0, BTCSDiffusion::BC_CONSTANT, + // 5. * std::pow(10, -6)); // set timestep for simulation to 1 second diffu.setTimestep(1.); @@ -33,7 +39,7 @@ int main(int argc, char *argv[]) { // loop 100 times // output is currently generated by the method itself for (int i = 0; i < 100; i++) { - diffu.simulate(field, alpha); + diffu.simulate(field.data(), alpha.data(), bc.data()); cout << "Iteration: " << i << "\n\n"; diff --git a/src/main_2D.cpp b/src/main_2D.cpp index fbcfaf5..e67004c 100644 --- a/src/main_2D.cpp +++ b/src/main_2D.cpp @@ -1,10 +1,14 @@ #include "BTCSDiffusion.hpp" // for BTCSDiffusion, BTCSDiffusion::BC_DIRICHLET +#include "BoundaryCondition.hpp" #include // for copy, max +#include #include #include // for std #include // for vector using namespace std; +using namespace Diffusion; + int main(int argc, char *argv[]) { // dimension of grid @@ -16,6 +20,7 @@ int main(int argc, char *argv[]) { // create input + diffusion coefficients for each grid cell std::vector alpha(n * m, 1 * pow(10, -1)); std::vector field(n * m, 1 * std::pow(10, -6)); + std::vector bc(n*m, {0,0}); // create instance of diffusion module BTCSDiffusion diffu(dim); @@ -23,9 +28,8 @@ int main(int argc, char *argv[]) { diffu.setXDimensions(1, n); diffu.setYDimensions(1, m); - // set the boundary condition for the left ghost cell to dirichlet - diffu.setBoundaryCondition(2, 2, BTCSDiffusion::BC_CONSTANT, - 5. * std::pow(10, -6)); + //set inlet to higher concentration without setting bc + field[12] = 5*std::pow(10,-3); // set timestep for simulation to 1 second diffu.setTimestep(1.); @@ -33,7 +37,7 @@ int main(int argc, char *argv[]) { cout << setprecision(12); for (int t = 0; t < 10; t++) { - diffu.simulate(field, alpha); + diffu.simulate(field.data(), alpha.data(), bc.data()); cout << "Iteration: " << t << "\n\n"; From 49128f922dca1f90cdf22d4de44272c0bc81d8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Mon, 28 Feb 2022 14:22:02 +0100 Subject: [PATCH 24/51] Update .gitlab-ci.yml --- .gitlab-ci.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b38c99..07072cf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,20 +3,41 @@ image: sobc/gitlab-ci stages: - build - test + - analysis before_script: - apt-get update && apt-get install -y libeigen3-dev build: stage: build + artifacts: + - build/src/test + - build/src/2D script: - mkdir build && cd build - cmake .. - make -lint: +run_1D: stage: test + dependencies: + - build + script: + - ./build/src/test + +run_2D: + stage: test + dependencies: + - build + script: + - ./build/src/2D + +lint: + stage: analysis script: - mkdir build && cd build - cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-checks=cppcoreguidelines-*,clang-analyzer-*,performance-*,readability-*, modernize-*" .. - make + only: + refs: + - master From 185c4ae4ba18e294b3f9b561e8e98c946a3898bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Mon, 28 Feb 2022 14:24:20 +0100 Subject: [PATCH 25/51] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 07072cf..639945f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,8 +11,9 @@ before_script: build: stage: build artifacts: - - build/src/test - - build/src/2D + paths: + - build/src/test + - build/src/2D script: - mkdir build && cd build - cmake .. @@ -30,7 +31,7 @@ run_2D: dependencies: - build script: - - ./build/src/2D + - ./build/src/2D lint: stage: analysis From 79ac2baa013a0079657033aa10b170425966f596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Mon, 28 Feb 2022 14:26:53 +0100 Subject: [PATCH 26/51] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 639945f..b437340 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,6 +14,7 @@ build: paths: - build/src/test - build/src/2D + expire_in: 100 script: - mkdir build && cd build - cmake .. From 6408fd89fedd7f41e87fb3dc8331908a6872cd61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Mon, 28 Feb 2022 14:27:06 +0100 Subject: [PATCH 27/51] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b437340..56bc933 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ build: paths: - build/src/test - build/src/2D - expire_in: 100 + expire_in: 100s script: - mkdir build && cd build - cmake .. From 6f9d344cee6979a0031bf569c1a7dbe8738af1bf Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Mon, 28 Feb 2022 15:09:46 +0100 Subject: [PATCH 28/51] Added new function simulate_base. - With this new function we abstract the actual filling of the A_Matrix and b_vector into processes which are indepent of the dimension. - This code will not run and so the pipeline will fail. --- src/BTCSDiffusion.cpp | 191 ++++++++++++++++++++++-------------------- src/BTCSDiffusion.hpp | 27 +++--- 2 files changed, 113 insertions(+), 105 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 274dd05..019c471 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -1,4 +1,5 @@ #include "BTCSDiffusion.hpp" +#include "BoundaryCondition.hpp" #include @@ -74,71 +75,73 @@ void Diffusion::BTCSDiffusion::updateInternals() { } } -void Diffusion::BTCSDiffusion::simulate1D( - Eigen::Map &c, Diffusion::boundary_condition left, - Diffusion::boundary_condition right, Eigen::Map &bc, - Eigen::Map &alpha, double dx, int size) { - - bool left_is_constant = (left.type == Diffusion::BC_CONSTANT); - bool right_is_constant = (right.type == Diffusion::BC_CONSTANT); +void Diffusion::BTCSDiffusion::simulate_base( + DVectorRowMajor &c, Eigen::Map &bc, + Eigen::Map &alpha, double dx, double time_step, + int size, DVectorRowMajor &t0_c) { // The sizes for matrix and vectors of the equation system is defined by the // actual size of the input vector and if the system is (partially) closed. // Then we will need ghost nodes. So this variable will give the count of // ghost nodes. - int bc_offset = !left_is_constant + !right_is_constant; - ; + // int bc_offset = !left_is_constant + !right_is_constant; + // ; // set sizes of private and yet allocated vectors - b_vector.resize(size + bc_offset); - x_vector.resize(size + bc_offset); + // b_vector.resize(size + bc_offset); + // x_vector.resize(size + bc_offset); - /* - * Begin to solve the equation system using LU solver of Eigen. - * - * But first fill the A matrix and b vector. - */ + // /* + // * Begin to solve the equation system using LU solver of Eigen. + // * + // * But first fill the A matrix and b vector. + // */ - // Set boundary condition for ghost nodes (for closed or flux system) or outer - // inlet nodes (constant boundary condition) - A_matrix.resize(size + bc_offset, size + bc_offset); - A_matrix.reserve(Eigen::VectorXi::Constant(size + bc_offset, 3)); + // // Set boundary condition for ghost nodes (for closed or flux system) or + // outer + // // inlet nodes (constant boundary condition) + // A_matrix.resize(size + bc_offset, size + bc_offset); + // A_matrix.reserve(Eigen::VectorXi::Constant(size + bc_offset, 3)); - A_matrix.insert(0, 0) = 1; - b_vector[0] = - (left_is_constant ? left.value : getBCFromFlux(left, c[0], alpha[0])); + // A_matrix.insert(0, 0) = 1; + // b_vector[0] = + // (left_is_constant ? left.value : getBCFromFlux(left, c[0], alpha[0])); - A_matrix.insert((size + bc_offset) - 1, (size + bc_offset) - 1) = 1; - b_vector[size + bc_offset - 1] = - (right_is_constant ? right.value - : getBCFromFlux(right, c[size - 1], alpha[size - 1])); + // A_matrix.insert(size + 1, size + 1) = 1; + // b_vector[size + 1] = + // (right_is_constant ? right.value + // : getBCFromFlux(right, c[size - 1], alpha[size - + // 1])); // Start filling the A matrix // =i= is used for equation system matrix and vector indexing // and =j= for indexing of c,alpha and bc - for (int i = 1, j = i + !(left_is_constant); i < size - right_is_constant; - i++, j++) { + // for (int i = 1, j = i + !(left_is_constant); i < size - right_is_constant; + // i++, j++) { - // if current grid cell is considered as constant boundary conditon - if (bc[j].type == Diffusion::BC_CONSTANT) { - A_matrix.insert(i, i) = 1; - b_vector[i] = bc[j].value; - continue; - } + // // if current grid cell is considered as constant boundary conditon + // if (bc[j].type == Diffusion::BC_CONSTANT) { + // A_matrix.insert(i, i) = 1; + // b_vector[i] = bc[j].value; + // continue; + // } - double sx = (alpha[j] * time_step) / (dx * dx); + // double sx = (alpha[j] * time_step) / (dx * dx); - A_matrix.insert(i, i) = -1. - 2. * sx; - A_matrix.insert(i, i - 1) = sx; - A_matrix.insert(i, i + 1) = sx; + // A_matrix.insert(i, i) = -1. - 2. * sx; + // A_matrix.insert(i, i - 1) = sx; + // A_matrix.insert(i, i + 1) = sx; - b_vector[i] = -c[j]; - } + // b_vector[i] = -c[j]; + // } + + fillMatrixFromRow(alpha, bc, size, dx, time_step); + fillVectorFromRowADI(c, alpha, bc, t0_c, size, dx, time_step); solveLES(); // write back result to input/output vector - c = x_vector.segment(!left_is_constant, c.size()); + // c = x_vector.segment(!left_is_constant, c.size()); } void Diffusion::BTCSDiffusion::simulate2D( @@ -165,7 +168,8 @@ void Diffusion::BTCSDiffusion::simulate2D( fillMatrixFromRow(alpha.row(i), n_cols, i, left_constant, right_constant, deltas[0], this->time_step / 2, bc.row(i)); - fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right, local_dt, bc.row(i)); + fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right, local_dt, + bc.row(i)); } solveLES(); @@ -197,7 +201,8 @@ void Diffusion::BTCSDiffusion::simulate2D( fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, deltas[1], this->time_step / 2, bc.col(i)); - fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right, local_dt, bc.col(i)); + fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right, local_dt, + bc.col(i)); } solveLES(); @@ -211,81 +216,85 @@ void Diffusion::BTCSDiffusion::simulate2D( c.transposeInPlace(); } -void Diffusion::BTCSDiffusion::fillMatrixFromRow(const DVectorRowMajor &alpha, - int n_cols, int row, - bool left_constant, - bool right_constant, - double delta, double time_step, - const BCVectorRowMajor &bc) { +inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( + const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, int size, + double dx, double time_step) { - n_cols += 2; - int offset = n_cols * row; + Diffusion::boundary_condition left = bc[0]; + Diffusion::boundary_condition right = bc[size - 1]; - A_matrix.insert(offset, offset) = 1; + bool left_constant = (left.type == Diffusion::BC_CONSTANT); + bool right_constant = (right.type == Diffusion::BC_CONSTANT); + + int A_size = A_matrix.cols(); + + A_matrix.insert(0, 0) = 1; if (left_constant) - A_matrix.insert(offset + 1, offset + 1) = 1; + A_matrix.insert(1, 1) = 1; - A_matrix.insert(offset + (n_cols - 1), offset + (n_cols - 1)) = 1; + A_matrix.insert(A_size - 1, A_size - 1) = 1; if (right_constant) - A_matrix.insert(offset + (n_cols - 2), offset + (n_cols - 2)) = 1; + A_matrix.insert(A_size - 2, A_size - 2) = 1; - for (int j = 1 + left_constant, k = j - 1; j < n_cols - (1 - right_constant); + for (int j = 1 + left_constant, k = j - 1; j < size - (1 - right_constant); j++, k++) { - double sx = (alpha[j - 1] * time_step) / (delta * delta); + double sx = (alpha[k] * time_step) / (dx * dx); if (bc[k].type == Diffusion::BC_CONSTANT) { - A_matrix.insert(offset + j, offset + j) = 1; + A_matrix.insert(j, j) = 1; continue; } - A_matrix.insert(offset + j, offset + j) = -1. - 2. * sx; - A_matrix.insert(offset + j, offset + (j - 1)) = sx; - A_matrix.insert(offset + j, offset + (j + 1)) = sx; + A_matrix.insert(j, j) = -1. - 2. * sx; + A_matrix.insert(j, (j - 1)) = sx; + A_matrix.insert(j, (j + 1)) = sx; } } -void Diffusion::BTCSDiffusion::fillVectorFromRowADI( - Eigen::Map &c, const Eigen::VectorXd alpha, int row, - double delta, boundary_condition left, boundary_condition right, - double time_step, const BCVectorRowMajor &bc) { +inline void Diffusion::BTCSDiffusion::fillVectorFromRowADI( + DVectorRowMajor &c, const Eigen::VectorXd alpha, const BCVectorRowMajor &bc, + DVectorRowMajor &t0_c, int size, double dx, double time_step) { - int ncol = c.cols(); - int nrow = c.rows(); - int offset = ncol + 2; + Diffusion::boundary_condition left = bc[0]; + Diffusion::boundary_condition right = bc[size - 1]; - if (left.type != Diffusion::BC_CONSTANT) { - // this is not correct currently.We will fix this when we are able to define - // FLUX boundary conditions - b_vector[offset * row] = getBCFromFlux(left, c(row, 0), alpha[0]); - } + bool left_constant = (left.type == Diffusion::BC_CONSTANT); + bool right_constant = (right.type == Diffusion::BC_CONSTANT); - if (right.type != Diffusion::BC_CONSTANT) { - b_vector[offset * row + (offset - 1)] = - getBCFromFlux(right, c(row, ncol - 1), alpha[ncol - 1]); - } + int b_size = b_vector.size(); - for (int j = 0; j < ncol; j++) { + for (int j = 0; j < size; j++) { boundary_condition tmp_bc = bc[j]; if (tmp_bc.type == Diffusion::BC_CONSTANT) { - b_vector[offset * row + (j + 1)] = tmp_bc.value; + b_vector[j + 1] = tmp_bc.value; continue; } - double y_values[3]; - y_values[0] = - (row != 0 ? c(row - 1, j) : getBCFromFlux(tmp_bc, c(row, j), alpha[j])); - y_values[1] = c(row, j); - y_values[2] = - (row != nrow - 1 ? c(row + 1, j) - : getBCFromFlux(tmp_bc, c(row, j), alpha[j])); + // double y_values[3]; + // y_values[0] = + // (row != 0 ? c(row - 1, j) : getBCFromFlux(tmp_bc, c(row, j), + // alpha[j])); + // y_values[1] = c(row, j); + // y_values[2] = + // (row != nrow - 1 ? c(row + 1, j) + // : getBCFromFlux(tmp_bc, c(row, j), alpha[j])); - double t0_c = - time_step * alpha[j] * - ((y_values[0] - 2 * y_values[1] + y_values[2]) / (delta * delta)); - b_vector[offset * row + (j + 1)] = -c(row, j) - (t0_c); + double t0_c_j = time_step * alpha[j] * (t0_c[j] / (dx * dx)); + b_vector[j + 1] = -c[j] - t0_c_j; + } + + if (!left_constant) { + // this is not correct currently.We will fix this when we are able to define + // FLUX boundary conditions + b_vector[0] = getBCFromFlux(left, b_vector[1], alpha[0]); + } + + if (!right_constant) { + b_vector[b_size - 1] = + getBCFromFlux(right, b_vector[size - 2], alpha[size - 1]); } } diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index d8c7cf7..6316f2d 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -112,24 +113,22 @@ private: Eigen::RowMajor> BCVectorRowMajor; - void simulate1D(Eigen::Map &c, - Diffusion::boundary_condition left, - Diffusion::boundary_condition right, - Eigen::Map &bc, - Eigen::Map &alpha, double dx, - int size); + void simulate_base(DVectorRowMajor &c, + Eigen::Map &bc, + Eigen::Map &alpha, double dx, + double time_step, int size, DVectorRowMajor &t0_c); void simulate2D(Eigen::Map &c, Eigen::Map &alpha, Eigen::Map &bc); - void fillMatrixFromRow(const DVectorRowMajor &alpha, int n_cols, int row, - bool left_constant, bool right_constant, double delta, - double time_step, const BCVectorRowMajor &bc); - void fillVectorFromRowADI(Eigen::Map &c, - const Eigen::VectorXd alpha, int row, double delta, - Diffusion::boundary_condition left, - Diffusion::boundary_condition right, - double time_step, const BCVectorRowMajor &bc); + inline void fillMatrixFromRow(const DVectorRowMajor &alpha, + const BCVectorRowMajor &bc, int size, double dx, + double time_step); + inline void fillVectorFromRowADI(DVectorRowMajor &c, + const Eigen::VectorXd alpha, + const BCVectorRowMajor &bc, + DVectorRowMajor &t0_c, int size, double dx, + double time_step); void simulate3D(std::vector &c); inline double getBCFromFlux(Diffusion::boundary_condition bc, double nearest_value, double neighbor_alpha); From 9a760bd9d98eccd38c82a5fa3363c1fdece42698 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Mon, 28 Feb 2022 20:23:50 +0100 Subject: [PATCH 29/51] Rewrite simulate1D function --- src/BTCSDiffusion.cpp | 143 ++++++++++++++++++++++++++---------------- src/BTCSDiffusion.hpp | 25 +++++--- 2 files changed, 107 insertions(+), 61 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 019c471..d615d75 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -15,6 +15,8 @@ #include +#define BTCS_MAX_DEP_PER_CELL 3 + Diffusion::BTCSDiffusion::BTCSDiffusion(unsigned int dim) : grid_dim(dim) { assert(dim <= 3); @@ -144,81 +146,116 @@ void Diffusion::BTCSDiffusion::simulate_base( // c = x_vector.segment(!left_is_constant, c.size()); } +inline void Diffusion::BTCSDiffusion::reserveMemory(int size, + int max_count_per_line) { + size += 2; + + A_matrix.resize(size, size); + A_matrix.reserve(Eigen::VectorXi::Constant(size, max_count_per_line)); + + b_vector.resize(size); + x_vector.resize(size); +} + +void Diffusion::BTCSDiffusion::simulate1D( + Eigen::Map &c, Eigen::Map &alpha, + Eigen::Map &bc) { + + int size = this->grid_cells[0]; + double dx = this->deltas[0]; + double time_step = this->time_step; + + reserveMemory(size, BTCS_MAX_DEP_PER_CELL); + + fillMatrixFromRow(alpha.row(0), bc.row(0), size, dx, time_step); + fillVectorFromRowADI(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, + dx, time_step); + + std::cout << A_matrix << std::endl; + + solveLES(); + + c = x_vector.segment(1, size); +} + void Diffusion::BTCSDiffusion::simulate2D( Eigen::Map &c, Eigen::Map &alpha, Eigen::Map &bc) { - double local_dt = this->time_step / 2.; - DMatrixRowMajor tmp_vector; + // double local_dt = this->time_step / 2.; + // DMatrixRowMajor tmp_vector; - int n_cols = c.cols(); - unsigned int size = (this->grid_cells[0] + 2) * (this->grid_cells[1]); + // int n_cols = c.cols(); + // unsigned int size = (this->grid_cells[0] + 2) * (this->grid_cells[1]); - A_matrix.resize(size, size); - A_matrix.reserve(Eigen::VectorXi::Constant(size, 3)); + // A_matrix.resize(size, size); + // A_matrix.reserve(Eigen::VectorXi::Constant(size, 3)); - b_vector.resize(size); - x_vector.resize(size); + // b_vector.resize(size); + // x_vector.resize(size); - for (int i = 0; i < c.rows(); i++) { - boundary_condition left = bc(i, 0); - bool left_constant = left.type == Diffusion::BC_CONSTANT; - boundary_condition right = bc(i, n_cols - 1); - bool right_constant = right.type == Diffusion::BC_CONSTANT; + // for (int i = 0; i < c.rows(); i++) { + // boundary_condition left = bc(i, 0); + // bool left_constant = left.type == Diffusion::BC_CONSTANT; + // boundary_condition right = bc(i, n_cols - 1); + // bool right_constant = right.type == Diffusion::BC_CONSTANT; - fillMatrixFromRow(alpha.row(i), n_cols, i, left_constant, right_constant, - deltas[0], this->time_step / 2, bc.row(i)); - fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right, local_dt, - bc.row(i)); - } + // fillMatrixFromRow(alpha.row(i), n_cols, i, left_constant, right_constant, + // deltas[0], this->time_step / 2, bc.row(i)); + // fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right, + // local_dt, + // bc.row(i)); + // } - solveLES(); + // solveLES(); - tmp_vector = x_vector; - tmp_vector.transposeInPlace(); - tmp_vector.conservativeResize(c.rows(), c.cols() + 2); + // tmp_vector = x_vector; + // tmp_vector.transposeInPlace(); + // tmp_vector.conservativeResize(c.rows(), c.cols() + 2); - Eigen::Map tmp(tmp_vector.data(), c.rows(), c.cols() + 2); + // Eigen::Map tmp(tmp_vector.data(), c.rows(), c.cols() + 2); - c = tmp_vector.block(0, 1, c.rows(), c.cols()); - c.transposeInPlace(); + // c = tmp_vector.block(0, 1, c.rows(), c.cols()); + // c.transposeInPlace(); - size = (this->grid_cells[0] * (this->grid_cells[1] + 2)); + // size = (this->grid_cells[0] * (this->grid_cells[1] + 2)); - A_matrix.resize(size, size); - A_matrix.reserve(Eigen::VectorXi::Constant(size, 3)); + // A_matrix.resize(size, size); + // A_matrix.reserve(Eigen::VectorXi::Constant(size, 3)); - b_vector.resize(size); - x_vector.resize(size); + // b_vector.resize(size); + // x_vector.resize(size); - n_cols = c.cols(); + // n_cols = c.cols(); - for (int i = 0; i < c.rows(); i++) { - boundary_condition left = bc(0, i); - bool left_constant = left.type == Diffusion::BC_CONSTANT; - boundary_condition right = bc(n_cols - 1, i); - bool right_constant = right.type == Diffusion::BC_CONSTANT; + // for (int i = 0; i < c.rows(); i++) { + // boundary_condition left = bc(0, i); + // bool left_constant = left.type == Diffusion::BC_CONSTANT; + // boundary_condition right = bc(n_cols - 1, i); + // bool right_constant = right.type == Diffusion::BC_CONSTANT; - fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, - deltas[1], this->time_step / 2, bc.col(i)); - fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right, local_dt, - bc.col(i)); - } + // fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, + // deltas[1], this->time_step / 2, bc.col(i)); + // fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right, + // local_dt, + // bc.col(i)); + // } - solveLES(); + // solveLES(); - tmp_vector = x_vector; - tmp_vector.transposeInPlace(); - tmp_vector.conservativeResize(c.rows(), c.cols() + 2); + // tmp_vector = x_vector; + // tmp_vector.transposeInPlace(); + // tmp_vector.conservativeResize(c.rows(), c.cols() + 2); - c = tmp_vector.block(0, 1, c.rows(), c.cols()); + // c = tmp_vector.block(0, 1, c.rows(), c.cols()); - c.transposeInPlace(); + // c.transposeInPlace(); } inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( - const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, int size, - double dx, double time_step) { + const Eigen::VectorXd &alpha, + const Eigen::Vector &bc, + int size, double dx, double time_step) { Diffusion::boundary_condition left = bc[0]; Diffusion::boundary_condition right = bc[size - 1]; @@ -254,8 +291,9 @@ inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( } inline void Diffusion::BTCSDiffusion::fillVectorFromRowADI( - DVectorRowMajor &c, const Eigen::VectorXd alpha, const BCVectorRowMajor &bc, - DVectorRowMajor &t0_c, int size, double dx, double time_step) { + const DVectorRowMajor &c, const Eigen::VectorXd alpha, + const BCVectorRowMajor &bc, const DVectorRowMajor &t0_c, int size, + double dx, double time_step) { Diffusion::boundary_condition left = bc[0]; Diffusion::boundary_condition right = bc[size - 1]; @@ -309,8 +347,7 @@ void Diffusion::BTCSDiffusion::simulate(double *c, double *alpha, Eigen::Map alpha_in(alpha, this->grid_cells[0]); Eigen::Map bc_in(bc, this->grid_cells[0]); - simulate1D(c_in, bc[0], bc[this->grid_cells[0] - 1], bc_in, alpha_in, - this->deltas[0], this->grid_cells[0]); + simulate1D(c_in, alpha_in, bc_in); } if (this->grid_dim == 2) { Eigen::Map c_in(c, this->grid_cells[1], diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 6316f2d..72a54b9 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -12,6 +12,8 @@ #include #include +#define BTCS_MAX_DEP_PER_CELL 3 + namespace Diffusion { /*! * Class implementing a solution for a 1/2/3D diffusion equation using backward @@ -113,23 +115,30 @@ private: Eigen::RowMajor> BCVectorRowMajor; - void simulate_base(DVectorRowMajor &c, - Eigen::Map &bc, + void simulate_base(DVectorRowMajor &c, Eigen::Map &bc, Eigen::Map &alpha, double dx, double time_step, int size, DVectorRowMajor &t0_c); + + void simulate1D(Eigen::Map &c, + Eigen::Map &alpha, + Eigen::Map &bc); + void simulate2D(Eigen::Map &c, Eigen::Map &alpha, Eigen::Map &bc); - inline void fillMatrixFromRow(const DVectorRowMajor &alpha, - const BCVectorRowMajor &bc, int size, double dx, - double time_step); - inline void fillVectorFromRowADI(DVectorRowMajor &c, + inline void fillMatrixFromRow( + const Eigen::VectorXd &alpha, + const Eigen::Vector &bc, + int size, double dx, double time_step); + inline void fillVectorFromRowADI(const DVectorRowMajor &c, const Eigen::VectorXd alpha, const BCVectorRowMajor &bc, - DVectorRowMajor &t0_c, int size, double dx, - double time_step); + const DVectorRowMajor &t0_c, int size, + double dx, double time_step); void simulate3D(std::vector &c); + + inline void reserveMemory(int size, int max_count_per_line); inline double getBCFromFlux(Diffusion::boundary_condition bc, double nearest_value, double neighbor_alpha); void solveLES(); From b7b37e92318ffcf31a71dbb0f850e6a73dab3b54 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 11:08:24 +0100 Subject: [PATCH 30/51] Update indexing + Bug fix - Wrong stopping criteria @ filling of matrix - Fill left and right side of b_vector with values from c instead of b_vector --- src/BTCSDiffusion.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index d615d75..814a6a0 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -171,8 +171,6 @@ void Diffusion::BTCSDiffusion::simulate1D( fillVectorFromRowADI(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, dx, time_step); - std::cout << A_matrix << std::endl; - solveLES(); c = x_vector.segment(1, size); @@ -275,7 +273,7 @@ inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( if (right_constant) A_matrix.insert(A_size - 2, A_size - 2) = 1; - for (int j = 1 + left_constant, k = j - 1; j < size - (1 - right_constant); + for (int j = 1 + left_constant, k = j - 1; k < size - right_constant; j++, k++) { double sx = (alpha[k] * time_step) / (dx * dx); @@ -327,12 +325,12 @@ inline void Diffusion::BTCSDiffusion::fillVectorFromRowADI( if (!left_constant) { // this is not correct currently.We will fix this when we are able to define // FLUX boundary conditions - b_vector[0] = getBCFromFlux(left, b_vector[1], alpha[0]); + b_vector[0] = getBCFromFlux(left, c[0], alpha[0]); } if (!right_constant) { b_vector[b_size - 1] = - getBCFromFlux(right, b_vector[size - 2], alpha[size - 1]); + getBCFromFlux(right, c[size - 1], alpha[size - 1]); } } From d65fcd453b25e0bafef4067ea04df75866477719 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 11:10:08 +0100 Subject: [PATCH 31/51] Upodate .gitlab-ci to run lint only @ main --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 56bc933..d3fc8a4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,4 +42,4 @@ lint: - make only: refs: - - master + - main From d0b75496c7c6d37f71c79ccb22a36aa491c3677c Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 11:18:55 +0100 Subject: [PATCH 32/51] Remove simulate_base function. --- src/BTCSDiffusion.cpp | 108 +++++++++++++++++++++--------------------- src/BTCSDiffusion.hpp | 7 +-- 2 files changed, 58 insertions(+), 57 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 814a6a0..71ff04b 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -77,74 +77,74 @@ void Diffusion::BTCSDiffusion::updateInternals() { } } -void Diffusion::BTCSDiffusion::simulate_base( - DVectorRowMajor &c, Eigen::Map &bc, - Eigen::Map &alpha, double dx, double time_step, - int size, DVectorRowMajor &t0_c) { +// void Diffusion::BTCSDiffusion::simulate_base( +// DVectorRowMajor &c, Eigen::Map &bc, +// Eigen::Map &alpha, double dx, double time_step, +// int size, DVectorRowMajor &t0_c) { - // The sizes for matrix and vectors of the equation system is defined by the - // actual size of the input vector and if the system is (partially) closed. - // Then we will need ghost nodes. So this variable will give the count of - // ghost nodes. - // int bc_offset = !left_is_constant + !right_is_constant; - // ; +// // The sizes for matrix and vectors of the equation system is defined by the +// // actual size of the input vector and if the system is (partially) closed. +// // Then we will need ghost nodes. So this variable will give the count of +// // ghost nodes. +// // int bc_offset = !left_is_constant + !right_is_constant; +// // ; - // set sizes of private and yet allocated vectors - // b_vector.resize(size + bc_offset); - // x_vector.resize(size + bc_offset); +// // set sizes of private and yet allocated vectors +// // b_vector.resize(size + bc_offset); +// // x_vector.resize(size + bc_offset); - // /* - // * Begin to solve the equation system using LU solver of Eigen. - // * - // * But first fill the A matrix and b vector. - // */ +// // /* +// // * Begin to solve the equation system using LU solver of Eigen. +// // * +// // * But first fill the A matrix and b vector. +// // */ - // // Set boundary condition for ghost nodes (for closed or flux system) or - // outer - // // inlet nodes (constant boundary condition) - // A_matrix.resize(size + bc_offset, size + bc_offset); - // A_matrix.reserve(Eigen::VectorXi::Constant(size + bc_offset, 3)); +// // // Set boundary condition for ghost nodes (for closed or flux system) or +// // outer +// // // inlet nodes (constant boundary condition) +// // A_matrix.resize(size + bc_offset, size + bc_offset); +// // A_matrix.reserve(Eigen::VectorXi::Constant(size + bc_offset, 3)); - // A_matrix.insert(0, 0) = 1; - // b_vector[0] = - // (left_is_constant ? left.value : getBCFromFlux(left, c[0], alpha[0])); +// // A_matrix.insert(0, 0) = 1; +// // b_vector[0] = +// // (left_is_constant ? left.value : getBCFromFlux(left, c[0], alpha[0])); - // A_matrix.insert(size + 1, size + 1) = 1; - // b_vector[size + 1] = - // (right_is_constant ? right.value - // : getBCFromFlux(right, c[size - 1], alpha[size - - // 1])); +// // A_matrix.insert(size + 1, size + 1) = 1; +// // b_vector[size + 1] = +// // (right_is_constant ? right.value +// // : getBCFromFlux(right, c[size - 1], alpha[size - +// // 1])); - // Start filling the A matrix - // =i= is used for equation system matrix and vector indexing - // and =j= for indexing of c,alpha and bc - // for (int i = 1, j = i + !(left_is_constant); i < size - right_is_constant; - // i++, j++) { +// // Start filling the A matrix +// // =i= is used for equation system matrix and vector indexing +// // and =j= for indexing of c,alpha and bc +// // for (int i = 1, j = i + !(left_is_constant); i < size - right_is_constant; +// // i++, j++) { - // // if current grid cell is considered as constant boundary conditon - // if (bc[j].type == Diffusion::BC_CONSTANT) { - // A_matrix.insert(i, i) = 1; - // b_vector[i] = bc[j].value; - // continue; - // } +// // // if current grid cell is considered as constant boundary conditon +// // if (bc[j].type == Diffusion::BC_CONSTANT) { +// // A_matrix.insert(i, i) = 1; +// // b_vector[i] = bc[j].value; +// // continue; +// // } - // double sx = (alpha[j] * time_step) / (dx * dx); +// // double sx = (alpha[j] * time_step) / (dx * dx); - // A_matrix.insert(i, i) = -1. - 2. * sx; - // A_matrix.insert(i, i - 1) = sx; - // A_matrix.insert(i, i + 1) = sx; +// // A_matrix.insert(i, i) = -1. - 2. * sx; +// // A_matrix.insert(i, i - 1) = sx; +// // A_matrix.insert(i, i + 1) = sx; - // b_vector[i] = -c[j]; - // } +// // b_vector[i] = -c[j]; +// // } - fillMatrixFromRow(alpha, bc, size, dx, time_step); - fillVectorFromRowADI(c, alpha, bc, t0_c, size, dx, time_step); +// fillMatrixFromRow(alpha, bc, size, dx, time_step); +// fillVectorFromRowADI(c, alpha, bc, t0_c, size, dx, time_step); - solveLES(); +// solveLES(); - // write back result to input/output vector - // c = x_vector.segment(!left_is_constant, c.size()); -} +// // write back result to input/output vector +// // c = x_vector.segment(!left_is_constant, c.size()); +// } inline void Diffusion::BTCSDiffusion::reserveMemory(int size, int max_count_per_line) { diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 72a54b9..ff64189 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -115,9 +115,10 @@ private: Eigen::RowMajor> BCVectorRowMajor; - void simulate_base(DVectorRowMajor &c, Eigen::Map &bc, - Eigen::Map &alpha, double dx, - double time_step, int size, DVectorRowMajor &t0_c); + // void simulate_base(DVectorRowMajor &c, Eigen::Map + // &bc, + // Eigen::Map &alpha, double dx, + // double time_step, int size, DVectorRowMajor &t0_c); void simulate1D(Eigen::Map &c, Eigen::Map &alpha, From 9ec382877ec1879a9fdb24bd61946e6a840eaf50 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 11:19:06 +0100 Subject: [PATCH 33/51] Fix function parameters. - Use private data types instead of plain Eigen types --- src/BTCSDiffusion.cpp | 6 +++--- src/BTCSDiffusion.hpp | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 71ff04b..f18916b 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -251,8 +251,8 @@ void Diffusion::BTCSDiffusion::simulate2D( } inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( - const Eigen::VectorXd &alpha, - const Eigen::Vector &bc, + const DVectorRowMajor &alpha, + const BCVectorRowMajor &bc, int size, double dx, double time_step) { Diffusion::boundary_condition left = bc[0]; @@ -289,7 +289,7 @@ inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( } inline void Diffusion::BTCSDiffusion::fillVectorFromRowADI( - const DVectorRowMajor &c, const Eigen::VectorXd alpha, + const DVectorRowMajor &c, const DVectorRowMajor alpha, const BCVectorRowMajor &bc, const DVectorRowMajor &t0_c, int size, double dx, double time_step) { diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index ff64189..fcbab40 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -128,12 +128,12 @@ private: Eigen::Map &alpha, Eigen::Map &bc); - inline void fillMatrixFromRow( - const Eigen::VectorXd &alpha, - const Eigen::Vector &bc, - int size, double dx, double time_step); + inline void fillMatrixFromRow(const DVectorRowMajor &alpha, + const BCVectorRowMajor &bc, int size, double dx, + double time_step); + inline void fillVectorFromRowADI(const DVectorRowMajor &c, - const Eigen::VectorXd alpha, + const DVectorRowMajor alpha, const BCVectorRowMajor &bc, const DVectorRowMajor &t0_c, int size, double dx, double time_step); From a5a66f1403ba1039befce5b933d841c1414efdbc Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 11:25:13 +0100 Subject: [PATCH 34/51] Update: pass additional information as parameter to simulate1D --- src/BTCSDiffusion.cpp | 10 ++++------ src/BTCSDiffusion.hpp | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index f18916b..555b213 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -159,11 +159,8 @@ inline void Diffusion::BTCSDiffusion::reserveMemory(int size, void Diffusion::BTCSDiffusion::simulate1D( Eigen::Map &c, Eigen::Map &alpha, - Eigen::Map &bc) { - - int size = this->grid_cells[0]; - double dx = this->deltas[0]; - double time_step = this->time_step; + Eigen::Map &bc, int size, double dx, + double time_step) { reserveMemory(size, BTCS_MAX_DEP_PER_CELL); @@ -345,7 +342,8 @@ void Diffusion::BTCSDiffusion::simulate(double *c, double *alpha, Eigen::Map alpha_in(alpha, this->grid_cells[0]); Eigen::Map bc_in(bc, this->grid_cells[0]); - simulate1D(c_in, alpha_in, bc_in); + simulate1D(c_in, alpha_in, bc_in, this->grid_cells[0], this->deltas[0], + this->time_step); } if (this->grid_dim == 2) { Eigen::Map c_in(c, this->grid_cells[1], diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index fcbab40..f6ce0f2 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -122,7 +122,8 @@ private: void simulate1D(Eigen::Map &c, Eigen::Map &alpha, - Eigen::Map &bc); + Eigen::Map &bc, int size, double dx, + double time_step); void simulate2D(Eigen::Map &c, Eigen::Map &alpha, From fb5ee6431e15e8403c0d455397419a3dece423f6 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 11:38:08 +0100 Subject: [PATCH 35/51] Update: also pass t0_c to simulate_1D --- src/BTCSDiffusion.cpp | 12 ++++++------ src/BTCSDiffusion.hpp | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 555b213..d8c875f 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -159,14 +159,13 @@ inline void Diffusion::BTCSDiffusion::reserveMemory(int size, void Diffusion::BTCSDiffusion::simulate1D( Eigen::Map &c, Eigen::Map &alpha, - Eigen::Map &bc, int size, double dx, - double time_step) { + Eigen::Map &bc, const DVectorRowMajor &t0_c, + int size, double dx, double time_step) { reserveMemory(size, BTCS_MAX_DEP_PER_CELL); fillMatrixFromRow(alpha.row(0), bc.row(0), size, dx, time_step); - fillVectorFromRowADI(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, - dx, time_step); + fillVectorFromRowADI(c, alpha, bc, t0_c, size, dx, time_step); solveLES(); @@ -342,8 +341,9 @@ void Diffusion::BTCSDiffusion::simulate(double *c, double *alpha, Eigen::Map alpha_in(alpha, this->grid_cells[0]); Eigen::Map bc_in(bc, this->grid_cells[0]); - simulate1D(c_in, alpha_in, bc_in, this->grid_cells[0], this->deltas[0], - this->time_step); + simulate1D(c_in, alpha_in, bc_in, + Eigen::VectorXd::Constant(this->grid_cells[0], 0), + this->grid_cells[0], this->deltas[0], this->time_step); } if (this->grid_dim == 2) { Eigen::Map c_in(c, this->grid_cells[1], diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index f6ce0f2..e17137b 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -122,7 +122,8 @@ private: void simulate1D(Eigen::Map &c, Eigen::Map &alpha, - Eigen::Map &bc, int size, double dx, + Eigen::Map &bc, + const DVectorRowMajor &t0_c, int size, double dx, double time_step); void simulate2D(Eigen::Map &c, From d0072f9f32d3ec2bf96617637408c779ee969b21 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 13:01:03 +0100 Subject: [PATCH 36/51] Revert to commit d65fcd4. --- src/BTCSDiffusion.cpp | 130 +++++++++++++++++++++--------------------- src/BTCSDiffusion.hpp | 12 ++-- 2 files changed, 69 insertions(+), 73 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index d8c875f..5da512c 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -77,74 +77,74 @@ void Diffusion::BTCSDiffusion::updateInternals() { } } -// void Diffusion::BTCSDiffusion::simulate_base( -// DVectorRowMajor &c, Eigen::Map &bc, -// Eigen::Map &alpha, double dx, double time_step, -// int size, DVectorRowMajor &t0_c) { +void Diffusion::BTCSDiffusion::simulate_base( + DVectorRowMajor &c, Eigen::Map &bc, + Eigen::Map &alpha, double dx, double time_step, + int size, DVectorRowMajor &t0_c) { -// // The sizes for matrix and vectors of the equation system is defined by the -// // actual size of the input vector and if the system is (partially) closed. -// // Then we will need ghost nodes. So this variable will give the count of -// // ghost nodes. -// // int bc_offset = !left_is_constant + !right_is_constant; -// // ; + // The sizes for matrix and vectors of the equation system is defined by the + // actual size of the input vector and if the system is (partially) closed. + // Then we will need ghost nodes. So this variable will give the count of + // ghost nodes. + // int bc_offset = !left_is_constant + !right_is_constant; + // ; -// // set sizes of private and yet allocated vectors -// // b_vector.resize(size + bc_offset); -// // x_vector.resize(size + bc_offset); + // set sizes of private and yet allocated vectors + // b_vector.resize(size + bc_offset); + // x_vector.resize(size + bc_offset); -// // /* -// // * Begin to solve the equation system using LU solver of Eigen. -// // * -// // * But first fill the A matrix and b vector. -// // */ + // /* + // * Begin to solve the equation system using LU solver of Eigen. + // * + // * But first fill the A matrix and b vector. + // */ -// // // Set boundary condition for ghost nodes (for closed or flux system) or -// // outer -// // // inlet nodes (constant boundary condition) -// // A_matrix.resize(size + bc_offset, size + bc_offset); -// // A_matrix.reserve(Eigen::VectorXi::Constant(size + bc_offset, 3)); + // // Set boundary condition for ghost nodes (for closed or flux system) or + // outer + // // inlet nodes (constant boundary condition) + // A_matrix.resize(size + bc_offset, size + bc_offset); + // A_matrix.reserve(Eigen::VectorXi::Constant(size + bc_offset, 3)); -// // A_matrix.insert(0, 0) = 1; -// // b_vector[0] = -// // (left_is_constant ? left.value : getBCFromFlux(left, c[0], alpha[0])); + // A_matrix.insert(0, 0) = 1; + // b_vector[0] = + // (left_is_constant ? left.value : getBCFromFlux(left, c[0], alpha[0])); -// // A_matrix.insert(size + 1, size + 1) = 1; -// // b_vector[size + 1] = -// // (right_is_constant ? right.value -// // : getBCFromFlux(right, c[size - 1], alpha[size - -// // 1])); + // A_matrix.insert(size + 1, size + 1) = 1; + // b_vector[size + 1] = + // (right_is_constant ? right.value + // : getBCFromFlux(right, c[size - 1], alpha[size - + // 1])); -// // Start filling the A matrix -// // =i= is used for equation system matrix and vector indexing -// // and =j= for indexing of c,alpha and bc -// // for (int i = 1, j = i + !(left_is_constant); i < size - right_is_constant; -// // i++, j++) { + // Start filling the A matrix + // =i= is used for equation system matrix and vector indexing + // and =j= for indexing of c,alpha and bc + // for (int i = 1, j = i + !(left_is_constant); i < size - right_is_constant; + // i++, j++) { -// // // if current grid cell is considered as constant boundary conditon -// // if (bc[j].type == Diffusion::BC_CONSTANT) { -// // A_matrix.insert(i, i) = 1; -// // b_vector[i] = bc[j].value; -// // continue; -// // } + // // if current grid cell is considered as constant boundary conditon + // if (bc[j].type == Diffusion::BC_CONSTANT) { + // A_matrix.insert(i, i) = 1; + // b_vector[i] = bc[j].value; + // continue; + // } -// // double sx = (alpha[j] * time_step) / (dx * dx); + // double sx = (alpha[j] * time_step) / (dx * dx); -// // A_matrix.insert(i, i) = -1. - 2. * sx; -// // A_matrix.insert(i, i - 1) = sx; -// // A_matrix.insert(i, i + 1) = sx; + // A_matrix.insert(i, i) = -1. - 2. * sx; + // A_matrix.insert(i, i - 1) = sx; + // A_matrix.insert(i, i + 1) = sx; -// // b_vector[i] = -c[j]; -// // } + // b_vector[i] = -c[j]; + // } -// fillMatrixFromRow(alpha, bc, size, dx, time_step); -// fillVectorFromRowADI(c, alpha, bc, t0_c, size, dx, time_step); + fillMatrixFromRow(alpha, bc, size, dx, time_step); + fillVectorFromRowADI(c, alpha, bc, t0_c, size, dx, time_step); -// solveLES(); + solveLES(); -// // write back result to input/output vector -// // c = x_vector.segment(!left_is_constant, c.size()); -// } + // write back result to input/output vector + // c = x_vector.segment(!left_is_constant, c.size()); +} inline void Diffusion::BTCSDiffusion::reserveMemory(int size, int max_count_per_line) { @@ -159,13 +159,17 @@ inline void Diffusion::BTCSDiffusion::reserveMemory(int size, void Diffusion::BTCSDiffusion::simulate1D( Eigen::Map &c, Eigen::Map &alpha, - Eigen::Map &bc, const DVectorRowMajor &t0_c, - int size, double dx, double time_step) { + Eigen::Map &bc) { + + int size = this->grid_cells[0]; + double dx = this->deltas[0]; + double time_step = this->time_step; reserveMemory(size, BTCS_MAX_DEP_PER_CELL); fillMatrixFromRow(alpha.row(0), bc.row(0), size, dx, time_step); - fillVectorFromRowADI(c, alpha, bc, t0_c, size, dx, time_step); + fillVectorFromRowADI(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, + dx, time_step); solveLES(); @@ -247,9 +251,8 @@ void Diffusion::BTCSDiffusion::simulate2D( } inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( - const DVectorRowMajor &alpha, - const BCVectorRowMajor &bc, - int size, double dx, double time_step) { + const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, int size, + double dx, double time_step) { Diffusion::boundary_condition left = bc[0]; Diffusion::boundary_condition right = bc[size - 1]; @@ -325,8 +328,7 @@ inline void Diffusion::BTCSDiffusion::fillVectorFromRowADI( } if (!right_constant) { - b_vector[b_size - 1] = - getBCFromFlux(right, c[size - 1], alpha[size - 1]); + b_vector[b_size - 1] = getBCFromFlux(right, c[size - 1], alpha[size - 1]); } } @@ -341,9 +343,7 @@ void Diffusion::BTCSDiffusion::simulate(double *c, double *alpha, Eigen::Map alpha_in(alpha, this->grid_cells[0]); Eigen::Map bc_in(bc, this->grid_cells[0]); - simulate1D(c_in, alpha_in, bc_in, - Eigen::VectorXd::Constant(this->grid_cells[0], 0), - this->grid_cells[0], this->deltas[0], this->time_step); + simulate1D(c_in, alpha_in, bc_in); } if (this->grid_dim == 2) { Eigen::Map c_in(c, this->grid_cells[1], diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index e17137b..7097fa2 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -115,16 +115,13 @@ private: Eigen::RowMajor> BCVectorRowMajor; - // void simulate_base(DVectorRowMajor &c, Eigen::Map - // &bc, - // Eigen::Map &alpha, double dx, - // double time_step, int size, DVectorRowMajor &t0_c); + void simulate_base(DVectorRowMajor &c, Eigen::Map &bc, + Eigen::Map &alpha, double dx, + double time_step, int size, DVectorRowMajor &t0_c); void simulate1D(Eigen::Map &c, Eigen::Map &alpha, - Eigen::Map &bc, - const DVectorRowMajor &t0_c, int size, double dx, - double time_step); + Eigen::Map &bc); void simulate2D(Eigen::Map &c, Eigen::Map &alpha, @@ -133,7 +130,6 @@ private: inline void fillMatrixFromRow(const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, int size, double dx, double time_step); - inline void fillVectorFromRowADI(const DVectorRowMajor &c, const DVectorRowMajor alpha, const BCVectorRowMajor &bc, From 9d3ee1f913029f7570510f25c35b63ccf2ec1612 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 13:13:33 +0100 Subject: [PATCH 37/51] Use simulate_base for actual solving of les. --- src/BTCSDiffusion.cpp | 85 ++++++++----------------------------------- src/BTCSDiffusion.hpp | 6 +-- 2 files changed, 18 insertions(+), 73 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 5da512c..2f6b477 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -76,74 +76,22 @@ void Diffusion::BTCSDiffusion::updateInternals() { deltas[i] = (double)domain_size[i] / grid_cells[i]; } } +void Diffusion::BTCSDiffusion::simulate_base(DVectorRowMajor &c, + const BCVectorRowMajor &bc, + const DVectorRowMajor &alpha, + double dx, double time_step, + int size, + const DVectorRowMajor &t0_c) { -void Diffusion::BTCSDiffusion::simulate_base( - DVectorRowMajor &c, Eigen::Map &bc, - Eigen::Map &alpha, double dx, double time_step, - int size, DVectorRowMajor &t0_c) { + reserveMemory(size, BTCS_MAX_DEP_PER_CELL); - // The sizes for matrix and vectors of the equation system is defined by the - // actual size of the input vector and if the system is (partially) closed. - // Then we will need ghost nodes. So this variable will give the count of - // ghost nodes. - // int bc_offset = !left_is_constant + !right_is_constant; - // ; - - // set sizes of private and yet allocated vectors - // b_vector.resize(size + bc_offset); - // x_vector.resize(size + bc_offset); - - // /* - // * Begin to solve the equation system using LU solver of Eigen. - // * - // * But first fill the A matrix and b vector. - // */ - - // // Set boundary condition for ghost nodes (for closed or flux system) or - // outer - // // inlet nodes (constant boundary condition) - // A_matrix.resize(size + bc_offset, size + bc_offset); - // A_matrix.reserve(Eigen::VectorXi::Constant(size + bc_offset, 3)); - - // A_matrix.insert(0, 0) = 1; - // b_vector[0] = - // (left_is_constant ? left.value : getBCFromFlux(left, c[0], alpha[0])); - - // A_matrix.insert(size + 1, size + 1) = 1; - // b_vector[size + 1] = - // (right_is_constant ? right.value - // : getBCFromFlux(right, c[size - 1], alpha[size - - // 1])); - - // Start filling the A matrix - // =i= is used for equation system matrix and vector indexing - // and =j= for indexing of c,alpha and bc - // for (int i = 1, j = i + !(left_is_constant); i < size - right_is_constant; - // i++, j++) { - - // // if current grid cell is considered as constant boundary conditon - // if (bc[j].type == Diffusion::BC_CONSTANT) { - // A_matrix.insert(i, i) = 1; - // b_vector[i] = bc[j].value; - // continue; - // } - - // double sx = (alpha[j] * time_step) / (dx * dx); - - // A_matrix.insert(i, i) = -1. - 2. * sx; - // A_matrix.insert(i, i - 1) = sx; - // A_matrix.insert(i, i + 1) = sx; - - // b_vector[i] = -c[j]; - // } - - fillMatrixFromRow(alpha, bc, size, dx, time_step); - fillVectorFromRowADI(c, alpha, bc, t0_c, size, dx, time_step); + fillMatrixFromRow(alpha.row(0), bc.row(0), size, dx, time_step); + fillVectorFromRowADI(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, + dx, time_step); solveLES(); - // write back result to input/output vector - // c = x_vector.segment(!left_is_constant, c.size()); + c = x_vector.segment(1, size); } inline void Diffusion::BTCSDiffusion::reserveMemory(int size, @@ -165,15 +113,12 @@ void Diffusion::BTCSDiffusion::simulate1D( double dx = this->deltas[0]; double time_step = this->time_step; - reserveMemory(size, BTCS_MAX_DEP_PER_CELL); + DVectorRowMajor input_field = c.row(0); - fillMatrixFromRow(alpha.row(0), bc.row(0), size, dx, time_step); - fillVectorFromRowADI(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, - dx, time_step); + simulate_base(input_field, bc, alpha, dx, time_step, size, + Eigen::VectorXd::Constant(size, 0)); - solveLES(); - - c = x_vector.segment(1, size); + c.row(0) << input_field; } void Diffusion::BTCSDiffusion::simulate2D( diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 7097fa2..6657f97 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -115,9 +115,9 @@ private: Eigen::RowMajor> BCVectorRowMajor; - void simulate_base(DVectorRowMajor &c, Eigen::Map &bc, - Eigen::Map &alpha, double dx, - double time_step, int size, DVectorRowMajor &t0_c); + void simulate_base(DVectorRowMajor &c, const BCVectorRowMajor &bc, + const DVectorRowMajor &alpha, double dx, + double time_step, int size, const DVectorRowMajor &t0_c); void simulate1D(Eigen::Map &c, Eigen::Map &alpha, From 9c1afe8e2d46313d22f8085e0c2155253322d89e Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 13:56:32 +0100 Subject: [PATCH 38/51] Implement 2D-row-wise in one direction --- src/BTCSDiffusion.cpp | 86 +++++++++++++++++++++++++++++++++---------- src/BTCSDiffusion.hpp | 8 +++- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 2f6b477..a015eb2 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -125,32 +126,28 @@ void Diffusion::BTCSDiffusion::simulate2D( Eigen::Map &c, Eigen::Map &alpha, Eigen::Map &bc) { - // double local_dt = this->time_step / 2.; - // DMatrixRowMajor tmp_vector; + int n_rows, n_cols; + + double local_dt = this->time_step / 2.; + double dx = this->deltas[0]; + DMatrixRowMajor tmp_vector; + + n_rows = this->grid_cells[1]; + n_cols = this->grid_cells[0]; - // int n_cols = c.cols(); // unsigned int size = (this->grid_cells[0] + 2) * (this->grid_cells[1]); - // A_matrix.resize(size, size); - // A_matrix.reserve(Eigen::VectorXi::Constant(size, 3)); + DMatrixRowMajor t0_c = calc_t0_c(c, alpha, bc, local_dt, dx); - // b_vector.resize(size); - // x_vector.resize(size); + std::cout << t0_c.row(0) << std::endl; - // for (int i = 0; i < c.rows(); i++) { - // boundary_condition left = bc(i, 0); - // bool left_constant = left.type == Diffusion::BC_CONSTANT; - // boundary_condition right = bc(i, n_cols - 1); - // bool right_constant = right.type == Diffusion::BC_CONSTANT; + for (int i = 0; i < n_rows; i++) { + DVectorRowMajor input_field = c.row(i); + simulate_base(input_field, bc.row(i), alpha.row(i), dx, local_dt, n_cols, t0_c.row(i)); + c.row(i) << input_field; + } - // fillMatrixFromRow(alpha.row(i), n_cols, i, left_constant, right_constant, - // deltas[0], this->time_step / 2, bc.row(i)); - // fillVectorFromRowADI(c, alpha.row(i), i, deltas[0], left, right, - // local_dt, - // bc.row(i)); - // } - - // solveLES(); + solveLES(); // tmp_vector = x_vector; // tmp_vector.transposeInPlace(); @@ -195,6 +192,55 @@ void Diffusion::BTCSDiffusion::simulate2D( // c.transposeInPlace(); } +Diffusion::BTCSDiffusion::DMatrixRowMajor Diffusion::BTCSDiffusion::calc_t0_c( + const DMatrixRowMajor &c, const DMatrixRowMajor &alpha, + const BCMatrixRowMajor &bc, double time_step, double dx) { + + int n_rows = this->grid_cells[1]; + int n_cols = this->grid_cells[0]; + + DMatrixRowMajor t0_c(n_rows, n_cols); + + double y_values[3]; + + // first, iterate over first row + for (int j = 0; j < n_cols; j++) { + y_values[0] = getBCFromFlux(bc(0, j), c(0, j), alpha(0, j)); + y_values[1] = c(0, j); + y_values[2] = c(1, j); + + t0_c(0, j) = time_step * alpha(0, j) * + (y_values[0] - 2 * y_values[1] + y_values[2]) / (dx * dx); + } + + // then iterate over inlet + for (int i = 1; i < n_rows - 1; i++) { + for (int j = 0; j < n_cols; j++) { + + y_values[0] = c(i - 1, j); + y_values[1] = c(i, j); + y_values[2] = c(i + 1, j); + + t0_c(i, j) = time_step * alpha(i, j) * + (y_values[0] - 2 * y_values[1] + y_values[2]) / (dx * dx); + } + } + + int end = n_rows - 1; + + // and finally over last row + for (int j = 0; j < n_cols; j++) { + y_values[0] = c(end-1,j); + y_values[1] = c(end, j); + y_values[2] = getBCFromFlux(bc(end, j), c(end, j), alpha(end, j)); + + t0_c(end, j) = time_step * alpha(end, j) * + (y_values[0] - 2 * y_values[1] + y_values[2]) / (dx * dx); + } + + return t0_c; +} + inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, int size, double dx, double time_step) { diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 6657f97..1c9556a 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -116,8 +116,8 @@ private: BCVectorRowMajor; void simulate_base(DVectorRowMajor &c, const BCVectorRowMajor &bc, - const DVectorRowMajor &alpha, double dx, - double time_step, int size, const DVectorRowMajor &t0_c); + const DVectorRowMajor &alpha, double dx, double time_step, + int size, const DVectorRowMajor &t0_c); void simulate1D(Eigen::Map &c, Eigen::Map &alpha, @@ -127,6 +127,10 @@ private: Eigen::Map &alpha, Eigen::Map &bc); + DMatrixRowMajor calc_t0_c(const DMatrixRowMajor &c, + const DMatrixRowMajor &alpha, + const BCMatrixRowMajor &bc, double time_step, double dx); + inline void fillMatrixFromRow(const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, int size, double dx, double time_step); From d4a87261518f8b141afedb31faea8e2e497502bd Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 14:05:23 +0100 Subject: [PATCH 39/51] Implement 2D-row-wise in both directions --- src/BTCSDiffusion.cpp | 71 ++++++++++++------------------------------- 1 file changed, 19 insertions(+), 52 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index a015eb2..4d0d195 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -127,69 +127,36 @@ void Diffusion::BTCSDiffusion::simulate2D( Eigen::Map &bc) { int n_rows, n_cols; + double dx; + DMatrixRowMajor t0_c; double local_dt = this->time_step / 2.; - double dx = this->deltas[0]; + dx = this->deltas[0]; DMatrixRowMajor tmp_vector; n_rows = this->grid_cells[1]; n_cols = this->grid_cells[0]; - // unsigned int size = (this->grid_cells[0] + 2) * (this->grid_cells[1]); - - DMatrixRowMajor t0_c = calc_t0_c(c, alpha, bc, local_dt, dx); - - std::cout << t0_c.row(0) << std::endl; + t0_c = calc_t0_c(c, alpha, bc, local_dt, dx); for (int i = 0; i < n_rows; i++) { DVectorRowMajor input_field = c.row(i); - simulate_base(input_field, bc.row(i), alpha.row(i), dx, local_dt, n_cols, t0_c.row(i)); + simulate_base(input_field, bc.row(i), alpha.row(i), dx, local_dt, n_cols, + t0_c.row(i)); c.row(i) << input_field; } - solveLES(); + dx = this->deltas[1]; - // tmp_vector = x_vector; - // tmp_vector.transposeInPlace(); - // tmp_vector.conservativeResize(c.rows(), c.cols() + 2); + t0_c = + calc_t0_c(c.transpose(), alpha.transpose(), bc.transpose(), local_dt, dx); - // Eigen::Map tmp(tmp_vector.data(), c.rows(), c.cols() + 2); - - // c = tmp_vector.block(0, 1, c.rows(), c.cols()); - // c.transposeInPlace(); - - // size = (this->grid_cells[0] * (this->grid_cells[1] + 2)); - - // A_matrix.resize(size, size); - // A_matrix.reserve(Eigen::VectorXi::Constant(size, 3)); - - // b_vector.resize(size); - // x_vector.resize(size); - - // n_cols = c.cols(); - - // for (int i = 0; i < c.rows(); i++) { - // boundary_condition left = bc(0, i); - // bool left_constant = left.type == Diffusion::BC_CONSTANT; - // boundary_condition right = bc(n_cols - 1, i); - // bool right_constant = right.type == Diffusion::BC_CONSTANT; - - // fillMatrixFromRow(alpha.col(i), n_cols, i, left_constant, right_constant, - // deltas[1], this->time_step / 2, bc.col(i)); - // fillVectorFromRowADI(c, alpha.row(i), i, deltas[1], left, right, - // local_dt, - // bc.col(i)); - // } - - // solveLES(); - - // tmp_vector = x_vector; - // tmp_vector.transposeInPlace(); - // tmp_vector.conservativeResize(c.rows(), c.cols() + 2); - - // c = tmp_vector.block(0, 1, c.rows(), c.cols()); - - // c.transposeInPlace(); + for (int i = 0; i < n_cols; i++) { + DVectorRowMajor input_field = c.col(i); + simulate_base(input_field, bc.col(i), alpha.col(i), dx, local_dt, n_rows, + t0_c.row(i)); + c.col(i) << input_field.transpose(); + } } Diffusion::BTCSDiffusion::DMatrixRowMajor Diffusion::BTCSDiffusion::calc_t0_c( @@ -221,8 +188,8 @@ Diffusion::BTCSDiffusion::DMatrixRowMajor Diffusion::BTCSDiffusion::calc_t0_c( y_values[1] = c(i, j); y_values[2] = c(i + 1, j); - t0_c(i, j) = time_step * alpha(i, j) * - (y_values[0] - 2 * y_values[1] + y_values[2]) / (dx * dx); + t0_c(i, j) = time_step * alpha(i, j) * + (y_values[0] - 2 * y_values[1] + y_values[2]) / (dx * dx); } } @@ -230,12 +197,12 @@ Diffusion::BTCSDiffusion::DMatrixRowMajor Diffusion::BTCSDiffusion::calc_t0_c( // and finally over last row for (int j = 0; j < n_cols; j++) { - y_values[0] = c(end-1,j); + y_values[0] = c(end - 1, j); y_values[1] = c(end, j); y_values[2] = getBCFromFlux(bc(end, j), c(end, j), alpha(end, j)); t0_c(end, j) = time_step * alpha(end, j) * - (y_values[0] - 2 * y_values[1] + y_values[2]) / (dx * dx); + (y_values[0] - 2 * y_values[1] + y_values[2]) / (dx * dx); } return t0_c; From 7ede42fb1a14aac7ef1ae2cfce3cbe71330e1e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Tue, 1 Mar 2022 14:09:54 +0100 Subject: [PATCH 40/51] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d3fc8a4..495bcc0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,6 +40,3 @@ lint: - mkdir build && cd build - cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-checks=cppcoreguidelines-*,clang-analyzer-*,performance-*,readability-*, modernize-*" .. - make - only: - refs: - - main From f0f73d417ceaaa981711ffd6b8c8ad2cfacfb0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Tue, 1 Mar 2022 14:13:04 +0100 Subject: [PATCH 41/51] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 495bcc0..e2f8368 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ image: sobc/gitlab-ci stages: - build - test - - analysis + - analyze before_script: - apt-get update && apt-get install -y libeigen3-dev @@ -35,8 +35,8 @@ run_2D: - ./build/src/2D lint: - stage: analysis + stage: analyze script: - - mkdir build && cd build + - mkdir lint && cd lint - cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-checks=cppcoreguidelines-*,clang-analyzer-*,performance-*,readability-*, modernize-*" .. - make From ec4bdf6a370faf4d4598b7405f72f5931f5e723e Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 15:34:56 +0100 Subject: [PATCH 42/51] Refactor code for better style --- src/BTCSDiffusion.cpp | 58 +++++++++++++++++++++---------------------- src/BTCSDiffusion.hpp | 23 ++++++++--------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index 4d0d195..c69b201 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -16,19 +17,20 @@ #include -#define BTCS_MAX_DEP_PER_CELL 3 +constexpr int BTCS_MAX_DEP_PER_CELL = 3; +constexpr int BTCS_2D_DT_SIZE = 2; Diffusion::BTCSDiffusion::BTCSDiffusion(unsigned int dim) : grid_dim(dim) { - assert(dim <= 3); grid_cells.resize(dim, 1); domain_size.resize(dim, 1); deltas.resize(dim, 1); + + this->time_step = 0; } void Diffusion::BTCSDiffusion::setXDimensions(double domain_size, unsigned int n_grid_cells) { - assert(this->grid_dim > 0); this->domain_size[0] = domain_size; this->grid_cells[0] = n_grid_cells; @@ -37,7 +39,6 @@ void Diffusion::BTCSDiffusion::setXDimensions(double domain_size, void Diffusion::BTCSDiffusion::setYDimensions(double domain_size, unsigned int n_grid_cells) { - assert(this->grid_dim > 1); this->domain_size[1] = domain_size; this->grid_cells[1] = n_grid_cells; @@ -46,29 +47,28 @@ void Diffusion::BTCSDiffusion::setYDimensions(double domain_size, void Diffusion::BTCSDiffusion::setZDimensions(double domain_size, unsigned int n_grid_cells) { - assert(this->grid_dim > 2); this->domain_size[2] = domain_size; this->grid_cells[2] = n_grid_cells; updateInternals(); } -unsigned int Diffusion::BTCSDiffusion::getXGridCellsN() { +auto Diffusion::BTCSDiffusion::getXGridCellsN() -> unsigned int { return this->grid_cells[0]; } -unsigned int Diffusion::BTCSDiffusion::getYGridCellsN() { +auto Diffusion::BTCSDiffusion::getYGridCellsN() -> unsigned int { return this->grid_cells[1]; } -unsigned int Diffusion::BTCSDiffusion::getZGridCellsN() { +auto Diffusion::BTCSDiffusion::getZGridCellsN() -> unsigned int { return this->grid_cells[2]; } -unsigned int Diffusion::BTCSDiffusion::getXDomainSize() { +auto Diffusion::BTCSDiffusion::getXDomainSize() -> double { return this->domain_size[0]; } -unsigned int Diffusion::BTCSDiffusion::getYDomainSize() { +auto Diffusion::BTCSDiffusion::getYDomainSize() -> double { return this->domain_size[1]; } -unsigned int Diffusion::BTCSDiffusion::getZDomainSize() { +auto Diffusion::BTCSDiffusion::getZDomainSize() -> double { return this->domain_size[2]; } @@ -87,7 +87,7 @@ void Diffusion::BTCSDiffusion::simulate_base(DVectorRowMajor &c, reserveMemory(size, BTCS_MAX_DEP_PER_CELL); fillMatrixFromRow(alpha.row(0), bc.row(0), size, dx, time_step); - fillVectorFromRowADI(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, + fillVectorFromRow(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, dx, time_step); solveLES(); @@ -126,16 +126,12 @@ void Diffusion::BTCSDiffusion::simulate2D( Eigen::Map &c, Eigen::Map &alpha, Eigen::Map &bc) { - int n_rows, n_cols; - double dx; + int n_rows = this->grid_cells[1]; + int n_cols = this->grid_cells[0]; + double dx = this->deltas[0]; DMatrixRowMajor t0_c; - double local_dt = this->time_step / 2.; - dx = this->deltas[0]; - DMatrixRowMajor tmp_vector; - - n_rows = this->grid_cells[1]; - n_cols = this->grid_cells[0]; + double local_dt = this->time_step / BTCS_2D_DT_SIZE; t0_c = calc_t0_c(c, alpha, bc, local_dt, dx); @@ -159,16 +155,18 @@ void Diffusion::BTCSDiffusion::simulate2D( } } -Diffusion::BTCSDiffusion::DMatrixRowMajor Diffusion::BTCSDiffusion::calc_t0_c( - const DMatrixRowMajor &c, const DMatrixRowMajor &alpha, - const BCMatrixRowMajor &bc, double time_step, double dx) { +auto Diffusion::BTCSDiffusion::calc_t0_c(const DMatrixRowMajor &c, + const DMatrixRowMajor &alpha, + const BCMatrixRowMajor &bc, + double time_step, double dx) + -> DMatrixRowMajor { int n_rows = this->grid_cells[1]; int n_cols = this->grid_cells[0]; DMatrixRowMajor t0_c(n_rows, n_cols); - double y_values[3]; + std::array y_values; // first, iterate over first row for (int j = 0; j < n_cols; j++) { @@ -222,15 +220,17 @@ inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( A_matrix.insert(0, 0) = 1; - if (left_constant) + if (left_constant) { A_matrix.insert(1, 1) = 1; + } A_matrix.insert(A_size - 1, A_size - 1) = 1; - if (right_constant) + if (right_constant) { A_matrix.insert(A_size - 2, A_size - 2) = 1; + } - for (int j = 1 + left_constant, k = j - 1; k < size - right_constant; + for (int j = 1 + (int)left_constant, k = j - 1; k < size - (int)right_constant; j++, k++) { double sx = (alpha[k] * time_step) / (dx * dx); @@ -245,8 +245,8 @@ inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( } } -inline void Diffusion::BTCSDiffusion::fillVectorFromRowADI( - const DVectorRowMajor &c, const DVectorRowMajor alpha, +inline void Diffusion::BTCSDiffusion::fillVectorFromRow( + const DVectorRowMajor &c, const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, const DVectorRowMajor &t0_c, int size, double dx, double time_step) { diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 1c9556a..3dad918 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -12,7 +12,6 @@ #include #include -#define BTCS_MAX_DEP_PER_CELL 3 namespace Diffusion { /*! @@ -64,28 +63,28 @@ public: /*! * Returns the number of grid cells in x direction. */ - unsigned int getXGridCellsN(); + auto getXGridCellsN() -> unsigned int; /*! * Returns the number of grid cells in y direction. */ - unsigned int getYGridCellsN(); + auto getYGridCellsN() -> unsigned int; /*! * Returns the number of grid cells in z direction. */ - unsigned int getZGridCellsN(); + auto getZGridCellsN() -> unsigned int; /*! * Returns the domain size in x direction. */ - unsigned int getXDomainSize(); + auto getXDomainSize() -> double; /*! * Returns the domain size in y direction. */ - unsigned int getYDomainSize(); + auto getYDomainSize() -> double; /*! * Returns the domain size in z direction. */ - unsigned int getZDomainSize(); + auto getZDomainSize() -> double; /*! * With given ghost zones simulate diffusion. Only 1D allowed at this moment. @@ -127,15 +126,15 @@ private: Eigen::Map &alpha, Eigen::Map &bc); - DMatrixRowMajor calc_t0_c(const DMatrixRowMajor &c, + auto calc_t0_c(const DMatrixRowMajor &c, const DMatrixRowMajor &alpha, - const BCMatrixRowMajor &bc, double time_step, double dx); + const BCMatrixRowMajor &bc, double time_step, double dx) -> DMatrixRowMajor; inline void fillMatrixFromRow(const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, int size, double dx, double time_step); - inline void fillVectorFromRowADI(const DVectorRowMajor &c, - const DVectorRowMajor alpha, + inline void fillVectorFromRow(const DVectorRowMajor &c, + const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, const DVectorRowMajor &t0_c, int size, double dx, double time_step); @@ -157,7 +156,7 @@ private: Eigen::VectorXd x_vector; double time_step; - int grid_dim; + unsigned int grid_dim; std::vector grid_cells; std::vector domain_size; From 1893929019cde9ed5188847690d3a37391c6c126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Tue, 1 Mar 2022 17:35:40 +0100 Subject: [PATCH 43/51] Added valgrind memcheck to CI. --- .gitlab-ci.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e2f8368..604a260 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,8 @@ image: sobc/gitlab-ci stages: - build - test - - analyze + - static_analyze + - dynamic_analyze before_script: - apt-get update && apt-get install -y libeigen3-dev @@ -35,8 +36,20 @@ run_2D: - ./build/src/2D lint: - stage: analyze + stage: static_analyze script: - mkdir lint && cd lint - cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-checks=cppcoreguidelines-*,clang-analyzer-*,performance-*,readability-*, modernize-*" .. - make + +memcheck_1D: + stage: dynamic_analyze + script: + - cd build/src + - valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes ./test 1>/dev/null + +memcheck_2D: + stage: dynamic_analyze + script: + - cd build/src + - valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes ./2D 1>/dev/null From 1f44e69e33788d7c5196ffe17d49c482b2b63132 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 19:42:10 +0100 Subject: [PATCH 44/51] Rename test application of 1D diffusion to '1D'. --- .gitlab-ci.yml | 4 ++-- src/CMakeLists.txt | 4 ++-- src/{main.cpp => main_1D.cpp} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename src/{main.cpp => main_1D.cpp} (100%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e2f8368..42cc52c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ build: stage: build artifacts: paths: - - build/src/test + - build/src/1D - build/src/2D expire_in: 100s script: @@ -25,7 +25,7 @@ run_1D: dependencies: - build script: - - ./build/src/test + - ./build/src/1D run_2D: stage: test diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f2982ab..49aff1d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,8 +1,8 @@ add_library(diffusion OBJECT BTCSDiffusion.cpp BTCSDiffusion.hpp) target_link_libraries(diffusion Eigen3::Eigen) -add_executable(test main.cpp) -target_link_libraries(test PUBLIC diffusion) +add_executable(1D main_1D.cpp) +target_link_libraries(1D PUBLIC diffusion) add_executable(2D main_2D.cpp) target_link_libraries(2D PUBLIC diffusion) diff --git a/src/main.cpp b/src/main_1D.cpp similarity index 100% rename from src/main.cpp rename to src/main_1D.cpp From 374a7ef9d9134b73a575280bd4b0c5e9774a83e3 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 19:48:18 +0100 Subject: [PATCH 45/51] Use range based loop for output. --- src/main_1D.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main_1D.cpp b/src/main_1D.cpp index 6432527..f7ec59b 100644 --- a/src/main_1D.cpp +++ b/src/main_1D.cpp @@ -43,8 +43,8 @@ int main(int argc, char *argv[]) { cout << "Iteration: " << i << "\n\n"; - for (int j = 0; j < field.size(); j++) { - cout << field[j] << "\n"; + for (auto & cell : field) { + cout << cell << "\n"; } cout << "\n" << endl; From aea3a7afc3326ac1fa318317363be57d497cf764 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 1 Mar 2022 19:50:15 +0100 Subject: [PATCH 46/51] Fix tidy infos in library. --- src/BTCSDiffusion.cpp | 29 +++++++++++------------------ src/BTCSDiffusion.hpp | 21 +++++++++++---------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/BTCSDiffusion.cpp b/src/BTCSDiffusion.cpp index c69b201..a9cd375 100644 --- a/src/BTCSDiffusion.cpp +++ b/src/BTCSDiffusion.cpp @@ -19,6 +19,7 @@ constexpr int BTCS_MAX_DEP_PER_CELL = 3; constexpr int BTCS_2D_DT_SIZE = 2; +constexpr double center_eq(double sx) { return -1. - 2. * sx; } Diffusion::BTCSDiffusion::BTCSDiffusion(unsigned int dim) : grid_dim(dim) { @@ -87,8 +88,8 @@ void Diffusion::BTCSDiffusion::simulate_base(DVectorRowMajor &c, reserveMemory(size, BTCS_MAX_DEP_PER_CELL); fillMatrixFromRow(alpha.row(0), bc.row(0), size, dx, time_step); - fillVectorFromRow(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, - dx, time_step); + fillVectorFromRow(c, alpha, bc, Eigen::VectorXd::Constant(size, 0), size, dx, + time_step); solveLES(); @@ -230,8 +231,8 @@ inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( A_matrix.insert(A_size - 2, A_size - 2) = 1; } - for (int j = 1 + (int)left_constant, k = j - 1; k < size - (int)right_constant; - j++, k++) { + for (int j = 1 + (int)left_constant, k = j - 1; + k < size - (int)right_constant; j++, k++) { double sx = (alpha[k] * time_step) / (dx * dx); if (bc[k].type == Diffusion::BC_CONSTANT) { @@ -239,7 +240,7 @@ inline void Diffusion::BTCSDiffusion::fillMatrixFromRow( continue; } - A_matrix.insert(j, j) = -1. - 2. * sx; + A_matrix.insert(j, j) = center_eq(sx); A_matrix.insert(j, (j - 1)) = sx; A_matrix.insert(j, (j + 1)) = sx; } @@ -266,15 +267,6 @@ inline void Diffusion::BTCSDiffusion::fillVectorFromRow( continue; } - // double y_values[3]; - // y_values[0] = - // (row != 0 ? c(row - 1, j) : getBCFromFlux(tmp_bc, c(row, j), - // alpha[j])); - // y_values[1] = c(row, j); - // y_values[2] = - // (row != nrow - 1 ? c(row + 1, j) - // : getBCFromFlux(tmp_bc, c(row, j), alpha[j])); - double t0_c_j = time_step * alpha[j] * (t0_c[j] / (dx * dx)); b_vector[j + 1] = -c[j] - t0_c_j; } @@ -317,11 +309,12 @@ void Diffusion::BTCSDiffusion::simulate(double *c, double *alpha, } } -inline double Diffusion::BTCSDiffusion::getBCFromFlux(boundary_condition bc, - double neighbor_c, - double neighbor_alpha) { +inline auto Diffusion::BTCSDiffusion::getBCFromFlux(boundary_condition bc, + double neighbor_c, + double neighbor_alpha) + -> double { - double val; + double val = 0; if (bc.type == Diffusion::BC_CLOSED) { val = neighbor_c; diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 3dad918..54e43c7 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -12,7 +12,6 @@ #include #include - namespace Diffusion { /*! * Class implementing a solution for a 1/2/3D diffusion equation using backward @@ -126,23 +125,25 @@ private: Eigen::Map &alpha, Eigen::Map &bc); - auto calc_t0_c(const DMatrixRowMajor &c, - const DMatrixRowMajor &alpha, - const BCMatrixRowMajor &bc, double time_step, double dx) -> DMatrixRowMajor; + auto calc_t0_c(const DMatrixRowMajor &c, const DMatrixRowMajor &alpha, + const BCMatrixRowMajor &bc, double time_step, double dx) + -> DMatrixRowMajor; inline void fillMatrixFromRow(const DVectorRowMajor &alpha, const BCVectorRowMajor &bc, int size, double dx, double time_step); inline void fillVectorFromRow(const DVectorRowMajor &c, - const DVectorRowMajor &alpha, - const BCVectorRowMajor &bc, - const DVectorRowMajor &t0_c, int size, - double dx, double time_step); + const DVectorRowMajor &alpha, + const BCVectorRowMajor &bc, + const DVectorRowMajor &t0_c, int size, + double dx, double time_step); void simulate3D(std::vector &c); inline void reserveMemory(int size, int max_count_per_line); - inline double getBCFromFlux(Diffusion::boundary_condition bc, - double nearest_value, double neighbor_alpha); + inline static auto getBCFromFlux(Diffusion::boundary_condition bc, + double neighbor_c, double neighbor_alpha) + -> double; + void solveLES(); void updateInternals(); From b0944bfba9a732eb80ee7eb03ce545413850203a Mon Sep 17 00:00:00 2001 From: Marco De Lucia Date: Wed, 2 Mar 2022 11:07:44 +0100 Subject: [PATCH 47/51] Added Comp2D.R and main_2D_mdl.cpp (src/CMakeLists.txt accordingly updated --- Comp2D.R | 143 ++++++++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 3 + src/main_2D_mdl.cpp | 66 ++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 Comp2D.R create mode 100644 src/main_2D_mdl.cpp diff --git a/Comp2D.R b/Comp2D.R new file mode 100644 index 0000000..7111103 --- /dev/null +++ b/Comp2D.R @@ -0,0 +1,143 @@ +## Time-stamp: "Last modified 2022-03-01 15:41:58 delucia" +library(ReacTran) +library(deSolve) +options(width=114) + +N <- 51 # number of grid cells +XX <- 1 # total size +dy <- dx <- XX/N # grid size +Dy <- Dx <- 0.1 # diffusion coeff, X- and Y-direction + +N2 <- ceiling(N/2) + +# The model equations + +Diff2D <- function (t, y, parms) { + CONC <- matrix(nrow = N, ncol = N, y) + # CONC[25, 25] <- 1 + dCONC <- tran.2D(CONC, D.x = Dx, D.y = Dy, dx = dx, dy = dy)$dC + + return (list(dCONC)) + +} + +# initial condition: 0 everywhere, except in central point +y <- matrix(nrow = N, ncol = N, data = 0) +y[N2, N2] <- 1 # initial concentration in the central point... + +as.numeric(y)[1301] + +# solve for 8 time units +times <- 0:5 +out <- ode.2D (y = y, func = Diff2D, t = times, parms = NULL, + dim = c(N,N), lrw=155412) + + +image(out, ask = FALSE, mfrow = c(2, 3), main = paste("time", times), asp=1) + +str(out) + +## adi <- data.matrix(data.table::fread("./bu2d/src/test2D", header=FALSE)) +## str(adi) + +adi <- data.table::fread("./bu2d/src/tt", header=FALSE) + +attributes(adi) <- attributes(out) + +madi1 <- matrix(adi[1,-1], 51,51,byrow = TRUE) +madi2 <- matrix(adi[2,-1], 51,51,byrow = TRUE) +madi6 <- matrix(adi[6,-1], 51,51,byrow = TRUE) +mout6 <- matrix(out[6,-1], 51,51,byrow = TRUE) + +madi6[1300] + + +madi6 <- matrix(unlist(adi[6,-1]), 501,501,byrow = TRUE) +class(madi6) +image(madi6, asp=1) +contour(madi6, asp=1) + +par(mfrow = c(1,3)) +image(madi1, asp=1) +image(madi2, asp=1) +image(madi5, asp=1) + +range(out[6, -1]) + +x11() + +plot(adi[6, -1], out[6, -1], pch=4) +abline(0,1) + +range(o6 <- out[6, -1]) +range(o2 <- out[2, -1]) + +image(madi6) + +contour(madi6) + +contour(mout6) + + +library(ReacTran) + +##################################################### +## FIRST SIMPLEST MODEL: Fick second law for diffusion +# d^2(C(x,t))/dx^2 = D. d^2(C(x,t))/dx^2 +# x: position +# t: time +# D: Diffusion coefficient +# C(x,t): Concentration at position x and time t + +# Model definition + +Fick_model=function(t=0,y,parms=NULL) +{ + + tran= tran.1D(C=y,D=1000,dx=grid)$dC + return(list(dC=tran)) +} + +# Grid definition + +C <- dnorm(seq(-10,10,.1)) +L <- 200 # length of the diffusion space +N <- length(C) # number of grid layers +grid <- setup.grid.1D(x.up = 0,L = L, N = N) + + +# Initial conditions + simulation + +Fick_solved_20 <- ode.1D(y = C, times=seq(0,20),func=Fick_model,nspec=1,method="lsoda") +Fick_solved_20000 <- ode.1D(y = C, times=seq(0,20000),func=Fick_model,nspec=1,method="lsoda") + + +# Plot Results + +plot(Fick_solved_20[1,2:dim(Fick_solved_20)[2]],type="l",xlab="x",ylab="Concentration",lwd=2,ylim=c(0,.5)) +par(new=T) +plot(Fick_solved_20[2,2:dim(Fick_solved_20)[2]],type="l",xlab="x",ylab="Concentration",lwd=2,ylim=c(0,.5),col="blue") +par(new=T) +plot(Fick_solved_20000[2,2:dim(Fick_solved_20000)[2]],type="l",xlab="x",ylab="Concentration",lwd=2,ylim=c(0,.5),col="red") + +Fick_solved_20[2,2:dim(Fick_solved_20)[2]]==Fick_solved_20000[2,2:dim(Fick_solved_20000)[2]] + +mass <- function(time, state, params) { + with(as.list(c(state, params)), { + H2O <- K3 * H * O + dH <- -K1 * H2O + dO <- -K2 * H2O + dH2O <- H2O + list(c(dH, dO, dH2O)) + }) +} + +params <- c(K1 = 2, + K2 = 1, + K3 = 0.005) +state <- c(H = 200, + O = 100, + H2O = 0) +time <- seq(0,5) +out <- ode(state, time, mass, params) +plot(out) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 49aff1d..0f82139 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,3 +6,6 @@ target_link_libraries(1D PUBLIC diffusion) add_executable(2D main_2D.cpp) target_link_libraries(2D PUBLIC diffusion) + +add_executable(Comp2D main_2D_mdl.cpp) +target_link_libraries(Comp2D PUBLIC diffusion) diff --git a/src/main_2D_mdl.cpp b/src/main_2D_mdl.cpp new file mode 100644 index 0000000..d9b73ff --- /dev/null +++ b/src/main_2D_mdl.cpp @@ -0,0 +1,66 @@ +#include "BTCSDiffusion.hpp" // for BTCSDiffusion, BTCSDiffusion::BC_DIRICHLET +#include "BoundaryCondition.hpp" +#include // for copy, max +#include +#include +#include // for std +#include // for vector +using namespace std; + +using namespace Diffusion; + +int main(int argc, char *argv[]) { + + // dimension of grid + int dim = 2; + + int n = 501; + int m = 501; + + // create input + diffusion coefficients for each grid cell + std::vector alpha(n * m, 1 * pow(10, -1)); + std::vector field(n * m, 0.); + std::vector bc(n*m, {0,0}); + + field[125500] = 1; + + // create instance of diffusion module + BTCSDiffusion diffu(dim); + + diffu.setXDimensions(1., n); + diffu.setYDimensions(1., m); + + // set the boundary condition for the left ghost cell to dirichlet + //diffu.setBoundaryCondition(250, 250, BTCSDiffusion::BC_CONSTANT, 1); + // for (int d=0; d<5;d++){ + // diffu.setBoundaryCondition(d, 0, BC_CONSTANT, .1); + // } + // diffu.setBoundaryCondition(1, 1, BTCSDiffusion::BC_CONSTANT, .1); + // diffu.setBoundaryCondition(1, 1, BTCSDiffusion::BC_CONSTANT, .1); + + // set timestep for simulation to 1 second + diffu.setTimestep(1.); + + cout << setprecision(7); + + // First we output the initial state + cout << 0; + + for (int i=0; i < m*n; i++) { + cout << "," << field[i]; + } + cout << endl; + + // Now we simulate and output 8 steps à 1 sec + for (int t = 1; t < 6; t++) { + diffu.simulate(field.data(), alpha.data(), bc.data()); + cout << t; + + for (int i=0; i < m*n; i++) { + cout << "," << field[i]; + } + cout << endl; + } + + return 0; +} From aea4c91e7aba9f629f6ac897f33333d70c9ae98d Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Wed, 2 Mar 2022 12:28:36 +0100 Subject: [PATCH 48/51] Checkout Comp.R from mdl branch --- Comp.R | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 Comp.R diff --git a/Comp.R b/Comp.R new file mode 100644 index 0000000..482e022 --- /dev/null +++ b/Comp.R @@ -0,0 +1,227 @@ +## Time-stamp: "Last modified 2022-01-25 11:22:30 delucia" +library(ReacTran) +library(deSolve) +options(width=114) + + +Diffusion <- function (t, Y, parms){ + tran <- tran.1D(C = Y, C.up = 1, C.down = 0, D = parms$D, dx = xgrid) + return(list(tran$dC)) +} + + +## sol 100 +## Set initial conditions +N <- 100 +xgrid <- setup.grid.1D(x.up = 0, x.down = 1, N = N) +x <- xgrid$x.mid +D.coeff <- 1E-3 +C0 <- 1 ## Initial concentration (mg/L) +X0 <- 0 ## Location of initial concentration (m) +Yini <- c(C0, rep(0,N-1)) +parms1 <- list(D=D.coeff) +times <- seq(from = 0, to = 5, by = 1) +system.time({ + out100 <- ode.1D(y = Yini, times = times, func = Diffusion, + parms = parms1, dimens = N) +}) + + +## sol 1000 +## Set initial conditions +N <- 1000 +xgrid <- setup.grid.1D(x.up = 0, x.down = 1, N = N) +x <- xgrid$x.mid +D.coeff <- 1E-3 +C0 <- 1 ## Initial concentration (mg/L) +X0 <- 0 ## Location of initial concentration (m) +Yini <- c(C0, rep(0,N-1)) +parms1 <- list(D=D.coeff) +times <- seq(from = 0, to = 5, by = 1) + +system.time({ + out1000 <- ode.1D(y = Yini, times = times, func = Diffusion, + parms = parms1, dimens = N) +}) + +## sol 5000 +## Set initial conditions +N <- 5000 +xgrid <- setup.grid.1D(x.up = 0, x.down = 1, N = N) +x <- xgrid$x.mid +D.coeff <- 1E-3 +C0 <- 1 ## Initial concentration (mg/L) +X0 <- 0 ## Location of initial concentration (m) +Yini <- c(C0, rep(0,N-1)) +parms1 <- list(D=D.coeff) +times <- seq(from = 0, to = 5, by = 1) +system.time({ + out5000 <- + ode.1D(y = Yini, times = times, func = Diffusion, parms = parms1, + dimens = N) +}) + +## ./mdl_main 100 > Sol100.dat +## ./mdl_main 1000 > Sol1000.dat +## ./mdl_main 5000 > Sol5000.dat +a100 <- data.table::fread("build/src/Sol100.dat") +a1000 <- data.table::fread("build/src/Sol1000.dat") +a5000 <- data.table::fread("build/src/Sol5000.dat") + + +## run: phreeqc pqc.in pqc.out /PATH_TO_PHREEQC.DAT +p100 <- data.table::fread("./selected_output_1.sel") + +pqc <- subset(p100, subset = time==5) |> + subset(subset = soln > 0 & soln < 101) |> + subset(select = Mg) + +cairo_pdf("test100.pdf", family="serif") +matplot.1D(out100, type = "l", subset = time == 4, xaxs="i", xlab="grid element", ylab="C", + main="Comparison ode.1D vs btcs, discretization 100", lwd=2) +lines(a100$inp1, lwd=2, col="blue") +lines(a100$inp2, lwd=2, col="red") +lines(a100$inp3, lwd=2, col="green4") +lines(pqc$Mg, lwd=2, col="orange") +legend("topright", c("R deSolve ode.1D reference, ts=1", + "btcs, ts=1", "btcs, ts=0.1","btcs, ts=5","PHREEQC ts=1"), lty="solid", + lwd=2, col=c("black","blue","red", "green4", "orange"), + bty="n") +dev.off() + +cairo_pdf("test1000.pdf", family="serif") +matplot.1D(out1000, type = "l", subset = time == 4, xaxs="i", xlab="grid element", ylab="C", + main="Comparison ode.1D vs btcs, discretization 1000", lwd=2) +lines(a1000$inp1, lwd=2, col="blue") +lines(a1000$inp2, lwd=2, col="red") +lines(a1000$inp3, lwd=2, col="green4") +legend("topright", c("R deSolve ode.1D reference, ts=1", + "btcs, ts=1", "btcs, ts=0.1","btcs, ts=5"), lty="solid", + lwd=2, col=c("black","blue","red", "green4"), + bty="n") +dev.off() + +cairo_pdf("test5000.pdf", family="serif") +matplot.1D(out5000, type = "l", subset = time == 4, xaxs="i", xlab="grid element", ylab="C", + main="Comparison ode.1D vs btcs, discretization 5000", lwd=2) +lines(a5000$inp1, lwd=2, col="blue") +lines(a5000$inp2, lwd=2, col="red") +lines(a5000$inp3, lwd=2, col="green4") +legend("topright", c("R deSolve ode.1D reference, ts=1", + "btcs, ts=1", "btcs, ts=0.1","btcs, ts=5"), lty="solid", + lwd=2, col=c("black","blue","red", "green4"), + bty="n") +dev.off() + + +############## old stuff + +a <- data.table::fread("build/src/Sol1000.dat") + +erfc <- function(x) 2 * pnorm(x * sqrt(2), lower = FALSE) + + +Analytical <- function(x, t, a, v) { + erfc +} + +cairo_pdf("test.pdf", family="serif") +matplot.1D(out, type = "l", subset = time == 4, xaxs="i", xlab="grid element", ylab="C", + main="Comparison ode.1D vs btcs", lwd=2) +lines(a$inp1, lwd=2, col="blue") +lines(a$inp2, lwd=2, col="red") +lines(a$inp3, lwd=2, col="green4") +legend("topright", c("R deSolve ode.1D reference, ts=1", + "btcs, ts=1", "btcs, ts=0.1","btcs, ts=5"), lty="solid", + lwd=2, col=c("black","blue","red", "green4"), + bty="n") +dev.off() + + +## sol 5000 +## Set initial conditions +N <- 5000 +xgrid <- setup.grid.1D(x.up = 0, x.down = 1, N = N) +x <- xgrid$x.mid +D.coeff <- 1E-3 +C0 <- 1 ## Initial concentration (mg/L) +X0 <- 0 ## Location of initial concentration (m) +Yini <- c(C0, rep(0,N-1)) + +parms1 <- list(D=D.coeff) + +times <- seq(from = 0, to = 5, by = 1) +system.time( + out5000 <- ode.1D(y = Yini, times = times, func = Diffusion, + parms = parms1, dimens = N)) + +a <- data.table::fread("build/src/Sol5000.dat") + +cairo_pdf("test.pdf", family="serif") +matplot.1D(out5000, type = "l", subset = time == 4, xaxs="i", xlab="grid element", ylab="C", + main="Comparison ode.1D vs btcs", lwd=2) +lines(a$inp1, lwd=2, col="blue") +lines(a$inp2, lwd=2, col="red") +lines(a$inp3, lwd=2, col="green4") +legend("topright", c("R deSolve ode.1D reference, ts=1", + "btcs, ts=1", "btcs, ts=0.1","btcs, ts=5"), lty="solid", + lwd=2, col=c("black","blue","red", "green4"), + bty="n") +dev.off() + + + +### ## +## diffusR <- function(t, C, parameters) { +## deltax <- c (0.5*delx, rep(delx, numboxes-1), 0.5*delx) +## Flux <- -D*diff(c(0, C, 0))/deltax +## dC <- -diff(Flux)/delx + +## list(dC) # the output +## } + +## ## ================== +## ## Model application +## ## ================== + +## ## the model parameters: +## D <- 0.3 # m2/day diffusion rate +## r <- 0.01 # /day net growth rate +## delx <- 1 # m thickness of boxes +## numboxes <- 50 + +## ## distance of boxes on plant, m, 1 m intervals +## Distance <- seq(from = 0.5, by = delx, length.out = numboxes) + +## ## Initial conditions, ind/m2 +## ## aphids present only on two central boxes +## C <- rep(0, times = numboxes) +## C[30:31] <- 1 +## state <- c(C = C) # initialise state variables + +## ## RUNNING the model: +## times <- seq(0, 200, by = 1) # output wanted at these time intervals +## out <- ode.band(state, times, diffusR, parms = 0, +## nspec = 1, names = "C") + +## ## ================ +## ## Plotting output +## ## ================ +## image(out, grid = Distance, method = "filled.contour", +## xlab = "time, days", ylab = "Distance on plant, m", +## main = "Aphid density on a row of plants") + +## matplot.1D(out, grid = Distance, type = "l", +## subset = time %in% seq(0, 200, by = 10)) + +## # add an observed dataset to 1-D plot (make sure to use correct name): +## data <- cbind(dist = c(0,10, 20, 30, 40, 50, 60), +## Aphid = c(0,0.1,0.25,0.5,0.25,0.1,0)) + +## matplot.1D(out, grid = Distance, type = "l", +## subset = time %in% seq(0, 200, by = 10), +## obs = data, obspar = list(pch = 18, cex = 2, col="red")) + +## ## Not run: +## plot.1D(out, grid = Distance, type = "l") + From caae08176b01c3a65ca8c6f0346b5852a53686f2 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 8 Mar 2022 14:49:56 +0100 Subject: [PATCH 49/51] Move application files to app dir --- .gitlab-ci.yml | 5 +++-- CMakeLists.txt | 1 + app/CMakeLists.txt | 8 ++++++++ {src => app}/main_1D.cpp | 0 {src => app}/main_2D.cpp | 0 {src => app}/main_2D_mdl.cpp | 0 src/CMakeLists.txt | 10 +--------- 7 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 app/CMakeLists.txt rename {src => app}/main_1D.cpp (100%) rename {src => app}/main_2D.cpp (100%) rename {src => app}/main_2D_mdl.cpp (100%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c06e7fa..6ce3bcd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,8 +13,9 @@ build: stage: build artifacts: paths: - - build/src/1D - - build/src/2D + - build/app/1D + - build/app/2D + - build/app/Comp2D expire_in: 100s script: - mkdir build && cd build diff --git a/CMakeLists.txt b/CMakeLists.txt index a18d9cc..068023f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,4 +7,5 @@ set(CMAKE_CXX_STANDARD 14) find_package(Eigen3 REQUIRED NO_MODULE) +add_subdirectory(app) add_subdirectory(src) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..c92d35b --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(1D main_1D.cpp) +target_link_libraries(1D PUBLIC diffusion) + +add_executable(2D main_2D.cpp) +target_link_libraries(2D PUBLIC diffusion) + +add_executable(Comp2D main_2D_mdl.cpp) +target_link_libraries(Comp2D PUBLIC diffusion) diff --git a/src/main_1D.cpp b/app/main_1D.cpp similarity index 100% rename from src/main_1D.cpp rename to app/main_1D.cpp diff --git a/src/main_2D.cpp b/app/main_2D.cpp similarity index 100% rename from src/main_2D.cpp rename to app/main_2D.cpp diff --git a/src/main_2D_mdl.cpp b/app/main_2D_mdl.cpp similarity index 100% rename from src/main_2D_mdl.cpp rename to app/main_2D_mdl.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0f82139..ffc3958 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,11 +1,3 @@ add_library(diffusion OBJECT BTCSDiffusion.cpp BTCSDiffusion.hpp) target_link_libraries(diffusion Eigen3::Eigen) - -add_executable(1D main_1D.cpp) -target_link_libraries(1D PUBLIC diffusion) - -add_executable(2D main_2D.cpp) -target_link_libraries(2D PUBLIC diffusion) - -add_executable(Comp2D main_2D_mdl.cpp) -target_link_libraries(Comp2D PUBLIC diffusion) +target_include_directories(diffusion PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) From 402405efdf931ccb73b9868201c9c6d27d140112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20L=C3=BCbke?= Date: Tue, 8 Mar 2022 14:58:14 +0100 Subject: [PATCH 50/51] Fix broken pipeline by setting new directory of applications. --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ce3bcd..2e36801 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,14 +27,14 @@ run_1D: dependencies: - build script: - - ./build/src/1D + - ./build/app/1D run_2D: stage: test dependencies: - build script: - - ./build/src/2D + - ./build/app/2D lint: stage: static_analyze @@ -46,11 +46,11 @@ lint: memcheck_1D: stage: dynamic_analyze script: - - cd build/src + - cd build/app - valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes ./1D 1>/dev/null memcheck_2D: stage: dynamic_analyze script: - - cd build/src + - cd build/app - valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes ./2D 1>/dev/null From d86f20456d376472d213210599b4d10de7ea4c52 Mon Sep 17 00:00:00 2001 From: Max Luebke Date: Tue, 8 Mar 2022 14:59:02 +0100 Subject: [PATCH 51/51] Adding some comments to library header. --- src/BTCSDiffusion.hpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/BTCSDiffusion.hpp b/src/BTCSDiffusion.hpp index 54e43c7..b994b80 100644 --- a/src/BTCSDiffusion.hpp +++ b/src/BTCSDiffusion.hpp @@ -88,9 +88,9 @@ public: /*! * With given ghost zones simulate diffusion. Only 1D allowed at this moment. * - * @param c Vector describing the concentration of one solution of the grid as - * continious memory (row major). - * @param alpha Vector of diffusion coefficients for each grid element. + * @param c Pointer to continious memory describing the current concentration state of each grid cell. + * @param alpha Pointer to memory area of diffusion coefficients for each grid element. + * @param bc Pointer to memory setting boundary conidition of each grid cell. */ void simulate(double *c, double *alpha, Diffusion::boundary_condition *bc); @@ -147,11 +147,6 @@ private: void solveLES(); void updateInternals(); - // std::vector bc; - // Eigen::Matrix - // bc; - Eigen::SparseMatrix A_matrix; Eigen::VectorXd b_vector; Eigen::VectorXd x_vector;