LoginSignup
8
8

More than 1 year has passed since last update.

C++で数値計算 (偏微分方程式)

Last updated at Posted at 2022-06-02

数値計算の基本をC++で書いていきます.
YonohaのGithubにも全体のコードとグラフを公開しています.

boostEigen, gnuplotを使用しているコードのコンパイルには, boostEigen, gnuplotのインストールがそれぞれ必要です.

変更した方が良い箇所があれば, この記事かTwitterの@Yonono_01まで連絡ください.

目次

偏微分方程式 (Partial Differential Equation)

線形偏微分方程式は楕円型/, 双曲型, 放物型のいずれかに分類される.
楕円型偏微分方程式にはPoisson方程式(において$f=0$としたLaplace方程式)やHelmholtz方程式などがある.
双曲型偏微分方程式には波動方程式や移流方程式などがある.
放物型偏微分方程式には拡散方程式やSchrödinger方程式などがある.
非線形偏微分方程式にはNavier Stokes方程式やKdV方程式などがある.

この記事では有限要素法を中心に説明する.

差分法 (Difference Method)

次の$1$次元拡散方程式

\frac{\partial u}{\partial t} = \frac{\partial^2 u}{\partial x^2} ~ (0 < x < 1, 0 < t < \infty)

を初期条件

u(x,0) = \sin(\pi x) ~ (0 \leq x \leq 1)

と境界条件

u(0,t) = u(1,t) = 0 ~ (0 < t < \infty)

の元で解く.
この偏微分方程式をメッシュ数を$I$として, 差分スキームの形に書きなおすと

差分方程式

u_i^{n+1} = u_i^n + \frac{d t}{dx^2} (u_{j+1}^n - 2u_j^n + u_{j-1}^n) ~ (n = 0, 1, \cdots, i = 1, \cdots, I-1)

初期条件

u_i^0 = \sin(\pi x_i)

境界条件

u_0^n = u_I^n = 0

のようになる.

#include <cmath>    // for std::sin
#include <fstream>  // for std::ofstream 
#include <iostream> // for std::cerr, std::endl
#include <vector>   // for std::vector
#include <boost/assert.hpp>                   // for BOOST_ASSERT
#include <boost/math/constants/constants.hpp> // for boost::math::constants::pi

namespace{
    using myvec = std::vector<double>;

    static auto constexpr TEND = 10.0;
    static auto constexpr TLOOP = 10000;
    static auto constexpr DT = TEND / TLOOP;
    static auto constexpr TREP = TLOOP / 1000;

    static auto constexpr MESH = 100;
    static auto constexpr XRANGE = 1.0;
    static auto constexpr DX = XRANGE / MESH;

    // boundary condition
    static auto constexpr BOUND_LEFT = 0.0;
    static auto constexpr BOUND_RIGHT = 0.0;
    
    // coefficient
    static auto constexpr DIFF = 0.01;
    static auto constexpr KAP = DIFF * DT / (DX * DX);
    
    // result output file
    bool output_result(std::vector<myvec> const &uxt);

    // discretization of x
    myvec discrete();

    // initial condition u(x, 0)
    myvec initial(myvec const &x);

    // center differential method
    std::vector<myvec> differencial_method(myvec const &u0);
}

int main(){
    // KAP > 0.5のとき強制終了 
    // Neumannの安定性解析より, KAP <= 0.5でないと正しい結果が得られない
    BOOST_ASSERT(KAP <= 0.5);

    auto x = discrete();

    auto u0 = initial(x);

    auto uxt = differencial_method(u0);

    output_result(uxt);

    if (!output_result(uxt)){
        std::cerr << "error! output file not open" << std::endl;
        return -1;
    }

    return 0;
}

namespace{
    bool output_result(std::vector<myvec> const &uxt){
        std::vector<myvec> u(uxt.size(), (myvec(TREP + 1)));
        
        // output only TREP + 1 (TLOOP = 0, 1000, ... , 10000)
        for (auto i = 0; i <= TREP; i++){
            for (auto j = 0; j <= MESH; j++){
                u[j][i] = uxt[j][1000 * i];
            }
        }

        std::ofstream ofs;
        ofs.open("data_center_diffe_diff.txt");

        if (!ofs){
            return false;
        }

        for (auto j = 0; j <= MESH; j++){
            ofs << static_cast<double>(j) * DX << " ";
            
            for (auto i = 0; i <= TREP; i++){
                ofs << u[j][i] << " ";
            }
            ofs << std::endl;
        }

        return true;
    }

    myvec discrete(){
        myvec x(MESH + 1, 0.0);

        // discrization of xrange
        for (int i = 0; i <= MESH; i++){
            x[i] = i * DX;
        }

        return x;
    }

    myvec initial(myvec const &x){
        myvec u0(MESH + 1, 0.0);

        // initial condition
        for (int i = 0; i<= MESH; i++){
            u0[i] = std::sin(boost::math::constants::pi<double>() * x[i]);
        }

        return u0;
    }

    std::vector<myvec> differencial_method(myvec const &u0){
        std::vector<myvec> uxt(MESH + 1, (myvec(TLOOP + 1)));

        // initial condition
        for (auto j = 0; j <= MESH; j++){
            uxt[j][0] = u0[j];
        }

        for (auto i = 0; i < TLOOP; i++){
            // boundary condition
            uxt[0][i + 1] = BOUND_LEFT;
            uxt[MESH][i + 1] = BOUND_RIGHT;

            // center differential method
            for (auto j = 1; j < MESH; j++){
                uxt[j][i + 1] = uxt[j][i] + KAP * (uxt[j + 1][i] - 2.0 * uxt[j][i] + uxt[j - 1][i]);
            }
        }

        return uxt;
    }
}

出力ファイルdata_center_diffe_diff.txtからデータを読み取り, gnuplotを用いてグラフを描き, center_diffe_diff.pngに保存する.

set title '1-dim diffusion equation by center differential method'

set xl 'x'
set yl 'u'
set grid

plot for [i = 2 : 12 : 2] 'data_center_diffe_diff.txt' using 1:i w l notitle

set terminal pngcairo
set output 'center_diffe_diff.png'
replot

center_diffe_diff.pngは次のようになる.

center_diffe_diff

有限要素法 (Finite Element Method)

有限要素法は微分方程式の弱形式をもとにした近似解法である.

Dirichlet条件, 左Neumann条件, 右Neumann条件のいずれにも対応するようにコードを書く.

1次元Poisson方程式

$f$を既知関数とし, $u$を求める未知関数とする.
このとき, 次の偏微分方程式

\Delta u + f = 0

をPoisson方程式という.
$1$次元においては

\frac{d^2 u}{dx^2} + f = 0 ~ (x \in [0, L])

となる.
今回は

f = 1

の場合を考える.

#include <fstream>  // for std::ofstream
#include <iostream> // for std::endl
#include <vector>   // fos std::vector
#include <boost/assert.hpp> // for BOOST_ASSERT

namespace fem{
	using myvec = std::vector<double>;

    class FEM final{
    	private:
        	enum class boundary_condi_type{
            	DIRICLET = 0,
            	LEFT_NEUMANN = 1,
            	RIGHT_NEUMANN = 2
        	};
        
        	static auto constexpr BCT = boundary_condi_type::LEFT_NEUMANN;

			// boundary conditions
        	static auto constexpr D0 = 0.0; // left Dirichlet
    		static auto constexpr D1 = 1.0; // right Direchlet
			static auto constexpr N0 = -1.0; // left Neumann
	    	static auto constexpr N1 = 0.5; // right Neumann

	    	static auto constexpr ELEMENT = 100;
        	static auto constexpr LENGTH = 1.0;
	    	static auto constexpr NODE = ELEMENT + 1;
        	static auto constexpr DX = LENGTH / ELEMENT;
	
    	private:
        	myvec bound_;
        	myvec diag_;
			myvec f_;
	    	myvec left_;
	    	myvec right_;

    	public:
        	FEM()
            	: bound_(NODE, 0.0), diag_(NODE, 0.0),f_(NODE), left_(NODE, 0.0), right_(NODE, 0.0) 
			{
				for (auto && elem : f_){
					elem = 1.0; // f = 1
				}

			}

        	~FEM() = default;

			// コピーコントラクタでcopy禁止
        	FEM(FEM const &dummy) = delete;

			// operator=()でもcopy禁止
        	FEM & operator=(FEM const &dummy) = delete;

			// boundary condition
        	void boundary();

			// make stiffness matrix
	    	void mat();;

			// TDMA method
	    	myvec tdma() const;

			// result output file
			bool output_file(myvec const &x);
    };
}

int main(){
    fem::FEM fem_obj;

	fem_obj.mat();
	fem_obj.boundary();
	auto const x = fem_obj.tdma();

	if (!fem_obj.output_file(x)){
        std::cerr << "output file not open" << std::endl;
        return -1;
    }

	return 0;
}

namespace fem{
	bool FEM::output_file(myvec const &x){
		std::ofstream ofs("data_Poisson.txt");
    	if (!ofs) {
        	return false;
    	}

		auto const size = static_cast<int>(x.size());

		for (auto i = 0; i < size; i++){
			ofs << fem::FEM::DX * static_cast<double>(i) << " " << x[i] << std::endl;
		}

		return true;
	}

	myvec FEM::tdma() const{
		myvec p(NODE, 0.0);
		myvec q(NODE, 0.0);

		p[0] = -right_[0] / diag_[0];
		q[0] = bound_[0] / diag_[0];

		for (auto i = 1; i < NODE; i++){
		    p[i] = -right_[i] / (diag_[i] + left_[i] * p[i - 1]);
		    q[i] = (bound_[i] - left_[i] * q[i - 1]) / (diag_[i] + left_[i] * p[i - 1]);
	    }

		myvec x(NODE, 0.0);
		x[NODE - 1] = q[NODE - 1];

		for (auto j = NODE - 2; j >= 0; j--){
			x[j] = p[j] * x[j + 1] + q[j];
		}
		
        return x;
 	}

    void FEM::boundary(){
		switch (BCT){
            case boundary_condi_type::DIRICLET:
            	diag_[0] = 1.0;
				diag_[NODE - 1] = 1.0;
			    left_[NODE - 1] = 0.0;
        	    right_[0] = 0.0;
				// boundary conditions
				bound_[0] = D0;
        	    bound_[NODE - 1] = D1;
                break;
		
            case boundary_condi_type::LEFT_NEUMANN:
        	    diag_[NODE - 1] = 1.0;
				left_[NODE - 1] = 0.0;
				// boundary conditions
			    bound_[0] -= N0; // left Neumann
				bound_[NODE - 1] = D1;
                break;
			
            case boundary_condi_type::RIGHT_NEUMANN:
			    diag_[0] = 1.0;
        	    right_[0] = 0.0;
				// boundary conditions
        	    bound_[0] = D0;
			    bound_[NODE - 1] += N1; // right Neumann
                break;
            
            default:
                BOOST_ASSERT(!"error! wrong boundary condition");
                break;
        }
	}

	void FEM::mat(){
	    for (auto i = 0; i < ELEMENT; i++){
			//boundary
		    bound_[i] += (2.0 * f_[i] + 1.0 * f_[i + 1]) * DX / 6.0;
		    bound_[i + 1] += (1.0 * f_[i] + 2.0 * f_[i + 1]) * DX / 6.0;

			// diffusion
		    diag_[i] += 1.0 / DX; 
            right_[i] -= 1.0 / DX;
			diag_[i + 1] += 1.0 / DX;
		    left_[i + 1] -= 1.0 / DX;
        }
	}
}

出力ファイルdata_Poisson.txtからデータを読み取り, gnuplotを用いてグラフを描き, Poisson.pngに保存する.

set title '1-dim Poisson equation f=1'

set xl 'x'
set yl 'u'
set grid

plot 'data_Poisson.txt' using 1:2 w l title 'fem 1-dim Poisson eq'

set terminal pngcairo
set output 'Poisson.png'
replot

Poisson.pngは次のようになる.

Poisson

1次元定常移流拡散方程式

移流速度$c$で流れ, 拡散係数$D$で拡散する移流拡散方程式は

\frac{\partial u}{\partial t} + \Delta \cdot (cu) 
= \Delta \cdot (D \Delta u)

と表せる.
$1$次元においては

\frac{\partial u}{\partial t} + c \frac{\partial u}{\partial x} 
= D \frac{\partial^2 u}{\partial t^2}

となる.
定常としたときは

c \frac{du}{dt} = D \frac{d^2 u}{d t^2}

となる.

前回の$1$次元Poisson方程式に移流行列を足し合わせれば良い.

#include <fstream>  // for std::ofstream
#include <iostream> // for std::endl
#include <vector>   // fos std::vector
#include <boost/assert.hpp> // for BOOST_ASSERT

namespace fem{
    class FEM final{
		public:
			using myvec = std::vector<double>;
	
    	private:
        	enum class boundary_condi_type{
            	DIRICLET = 0,
            	LEFT_NEUMANN = 1,
            	RIGHT_NEUMANN = 2
        	};
        
        	static auto constexpr BCT = boundary_condi_type::DIRICLET;

            // coefficient
            static auto constexpr C = 0.1;
            static auto constexpr DIFF = 0.01;

			// boundary conditions
        	static auto constexpr D0 = 0.0; // left Dirichlet
    		static auto constexpr D1 = 1.0; // right Direchlet
			static auto constexpr N0 = 0.5; // left Neumann
	    	static auto constexpr N1 = -1.0; // right Neumann

	    	static auto constexpr ELEMENT = 100;
        	static auto constexpr LENGTH = 1.0;
	    	static auto constexpr NODE = ELEMENT + 1;
    
    	public:
        	static auto constexpr DX = LENGTH / ELEMENT;
	
    	private:
        	myvec bound_;
        	myvec diag_;
			myvec f_;
	    	myvec left_;
	    	myvec right_;

    	public:
        	FEM()
            	: bound_(NODE, 0.0), diag_(NODE, 0.0), f_(NODE), left_(NODE, 0.0), right_(NODE, 0.0)
        	{
				for (auto && elem : f_){
					elem = 0.0; // (right hand side) = 0
				}
			}

        	~FEM() = default;

			// コピーコントラクタでcopy禁止
        	FEM(FEM const &dummy) = delete;

			// operator=()でもcopy禁止
        	FEM & operator=(FEM const &dummy) = delete;

			// boundary condition
        	void boundary();

			// make stiffness matrix
	    	void mat();

			// TDMA method
	    	myvec tdma() const;

			// result output file
			bool output_file(myvec const &x);
    };
}

int main(){
	fem::FEM fem_obj;

	fem_obj.mat();
	fem_obj.boundary();
	auto const x = fem_obj.tdma();

	if (!fem_obj.output_file(x)){
        std::cerr << "output file not open" << std::endl;
        return -1;
    }

	return 0;
}

namespace fem{
    void FEM::boundary(){
		switch (BCT){
            case boundary_condi_type::DIRICLET:
            	diag_[0] = 1.0;
				diag_[NODE - 1] = 1.0;
			    left_[NODE - 1] = 0.0;
        	    right_[0] = 0.0;
				// boundary conditions
				bound_[0] = D0;
        	    bound_[NODE - 1] = D1;
                break;
		
            case boundary_condi_type::LEFT_NEUMANN:
        	    diag_[NODE - 1] = 1.0;
				left_[NODE - 1] = 0.0;
				// boundary conditions
			    bound_[0] -= N0 * DIFF; // left Neumann
				bound_[NODE - 1] = D1;
                break;
			
            case boundary_condi_type::RIGHT_NEUMANN:
			    diag_[0] = 1.0;
        	    right_[0] = 0.0;
				// boundary conditions
        	    bound_[0] = D0;
			    bound_[NODE - 1] += N1 * DIFF; // right Neumann
                break;
            
            default:
                BOOST_ASSERT(!"error! wrong boundary condition");
                break;
        }
	}

	void FEM::mat(){
	    for (auto i = 0; i < ELEMENT; i++){
            // advection
            diag_[i] -= C / 2.0;
            right_[i] += C / 2.0;
            diag_[i + 1] += C / 2.0;
            left_[i + 1] -= C / 2.0; 

			//boundary
		    bound_[i] += (2.0 * f_[i] + 1.0 * f_[i + 1]) * DX / 6.0;
		    bound_[i + 1] += (1.0 * f_[i] + 2.0 * f_[i + 1]) * DX / 6.0;

			// diffusion
		    diag_[i] += DIFF / DX; 
            right_[i] -= DIFF / DX;
			diag_[i + 1] += DIFF / DX;
		    left_[i + 1] -= DIFF / DX; 
        }
	}

	FEM::myvec FEM::tdma() const{
		myvec p(NODE, 0.0);
		myvec q(NODE, 0.0);

		p[0] = -right_[0] / diag_[0];
		q[0] = bound_[0] / diag_[0];

		for (auto i = 1; i < NODE; i++){
		    p[i] = -right_[i] / (diag_[i] + left_[i] * p[i - 1]);
		    q[i] = (bound_[i] - left_[i] * q[i - 1]) / (diag_[i] + left_[i] * p[i - 1]);
	    }

		myvec x(NODE, 0.0);
		x[NODE - 1] = q[NODE - 1];

		for (auto j = NODE - 2; j >= 0; j--){
			x[j] = p[j] * x[j + 1] + q[j];
		}
		
        return x;
 	}

	bool FEM::output_file(myvec const &x){
		std::ofstream ofs("data_advec_diff.txt");
    	if (!ofs) {
        	return false;
    	}

		auto const size = static_cast<int>(x.size());

		for (auto i = 0; i < size; i++){
			ofs << fem::FEM::DX * static_cast<double>(i) << " " << x[i] << std::endl;
		}

		return true;
	}
}

出力ファイルdata_advec_diff.txtからデータを読み込み, gnuplotを用いてグラフを描き, advec_diff.pngに保存する.

set title 'steady-state advection-diffusion equation'

set xl 'x'
set yl 'u'
set grid

plot 'data_advec_diff.txt' using 1:2 w l title 'steady advec diff'

set terminal pngcairo
set output 'steady_advec_diff.png'
replot

advec_diff.pngは次のようになる.

steady_advec_diff

1次元非定常拡散方程式

拡散係数$D$で拡散する場合の拡散方程式は

\frac{\partial u}{\partial t} = \Delta \cdot (D \Delta u)

である.
$1$次元においては

\frac{\partial u}{\partial t} = D \frac{\partial^2 u}{\partial t^2}

となる.

#include <algorithm> // for std::min
#include <array>     // for std::array
#include <fstream>   // for std::ofstream
#include <iostream>  // for std::endl
#include <sstream>   // for std::string, std::stringstream
#include <vector>    // fos std::vector
#include <boost/assert.hpp> // for BOOST_ASSERT
#include <boost/format.hpp> // for boost::format

namespace fem{
    class FEM final{
		public:
			using myvec = std::vector<double>;
	
    	private:
        	enum class boundary_condi_type{
            	DIRICLET = 0,
            	LEFT_NEUMANN = 1,
            	RIGHT_NEUMANN = 2
        	};

            // DIRICLET or LEFT NEUMANN or RIGHT NEUMANN
        	static auto constexpr BCT = boundary_condi_type::RIGHT_NEUMANN;

			// boundary condition
        	static auto constexpr D0 = 0.0; // left Dirichlet
    		static auto constexpr D1 = 0.0; // right Direchlet
			static auto constexpr N0 = 1.0; // left Neumann
	    	static auto constexpr N1 = 0.5; // right Neumann

            // coefficient
            static auto constexpr DIFF = 0.01; // diffusion 
            static auto constexpr THETA = 0.5; // theta method

            // for discrete of x and t
 	    	static auto constexpr ELEMENT = 100; // mesh number
        	static auto constexpr LENGTH = 1.0;  // xrange
            static auto constexpr TEND = 10.0;   // trange
    
    	public:
			static auto constexpr TLOOP = 10000;
            static auto constexpr DT = TEND / TLOOP;
    		static auto constexpr TREP = TLOOP / 1000;
			static auto constexpr NODE = ELEMENT + 1;
			static auto constexpr DX = LENGTH / ELEMENT;
			static auto constexpr KAP = DIFF * DT / (DX * DX);
	
    	private:
        	myvec bound_;
        	myvec diag1_;
			myvec diag2_;
	    	myvec left1_;
			myvec left2_;
	    	myvec right1_;
	    	myvec right2_;
			myvec x_;

    	public:
        	FEM()
            	: bound_(NODE, 0.0), diag1_(NODE, 0.0), diag2_(NODE, 0.0), 
                  left1_(NODE, 0.0), left2_(NODE, 0.0), right1_(NODE, 0.0), right2_(NODE, 0.0), x_(NODE, 0.0)
        	{
				for (auto i = 0; i < NODE; i++){
					x_[i] = std::min(DX * static_cast<double>(i), 1.0 - DX * static_cast<double>(i)); // f = 
				}
			}

        	~FEM() = default;

			// copy constructorでcopy禁止
        	FEM(FEM const &dummy) = delete;

			// operator=()でもcopy禁止
        	FEM & operator=(FEM const &dummy) = delete;

			// boundary condition
        	void boundary();
			void boundary2();

			// TDMA method
	    	void tdma();

			// make stiffness matrix
	    	void mat();

			// output for each TLOOP
			bool result_output(int i);
    };
}

int main(){
    // KAP > 0.5のとき強制終了 
    // Neumannの安定性解析より, KAP <= 0.5でないと正しい結果が得られない
    BOOST_ASSERT(fem::FEM::KAP <= 0.5);

    fem::FEM fem_obj;

	fem_obj.result_output(0);

	if (!fem_obj.result_output(0)){
		std::cerr << "output file not open" << std::endl;
        return -1;
	}

	fem_obj.mat();
	fem_obj.boundary();

	for (auto i = 1; i <= fem::FEM::TLOOP; i++){
		fem_obj.boundary2();
		fem_obj.tdma();

		if (!(i % 1000)){
			if (!fem_obj.result_output(i)){
				std::cerr << "output file not open" << std::endl;
        		return -1;
			}
		}
	}

	return 0;
}

namespace fem{
	bool FEM::result_output(int i){
		std::stringstream ss;
		std::string name;
		std::ofstream ofs;

		ss << i;
		name = ss.str();
		name = "data_nonste_diff_" + name + ".txt";
		
		ofs.open(name.c_str());
		if (!ofs){
			return false;
		}

		for (auto j = 0; j < NODE; j++){
			ofs << DX * static_cast<double>(j) << " " << x_[j] << std::endl;
		}

		return true;
	}

    void FEM::boundary(){
		switch (BCT){
            case boundary_condi_type::DIRICLET:
            	diag1_[0] = 1.0;
				diag1_[NODE - 1] = 1.0;
                diag2_[0] = 1.0;
                diag2_[NODE - 1] = 1.0;
			    left1_[NODE - 1] = 0.0;
                left2_[NODE - 1] = 0.0;
        	    right1_[0] = 0.0;
                right2_[0] = 0.0;
                break;
		
            case boundary_condi_type::LEFT_NEUMANN:
        	    diag1_[NODE - 1] = 1.0;
                diag2_[NODE - 1] = 1.0;
				left1_[NODE - 1] = 0.0;
                left2_[NODE - 1] = 0.0;
                break;
			
            case boundary_condi_type::RIGHT_NEUMANN:
			    diag1_[0] = 1.0;
                diag2_[0] = 1.0;
        	    right1_[0] = 0.0;
                right2_[0] = 0.0;
                break;
            
            default:
                BOOST_ASSERT(!"error! wrong boundary condition");
                break;
        }
	}

    void FEM::boundary2(){
		switch (BCT){
            case boundary_condi_type::DIRICLET:
                bound_[0] = D0;
        	    bound_[NODE - 1] = D1;
                break;
		
            case boundary_condi_type::LEFT_NEUMANN:
                bound_[0] = diag2_[0] * x_[0] + right2_[0] * x_[1] - N0 * DIFF;
				bound_[NODE - 1] = D1;
                break;
			
            case boundary_condi_type::RIGHT_NEUMANN:
                bound_[0] = D0;  
				bound_[NODE - 1] = left2_[NODE - 1] * x_[NODE - 2] + left2_[NODE - 1] * x_[NODE - 1] + N1 * DIFF;
                break;
            
            default:
                BOOST_ASSERT(!"error! wrong boundary condition");
                break;
        }

		for (auto j = 1; j < NODE - 1; j++){
            bound_[j] = left2_[j] * x_[j - 1] + diag2_[j] * x_[j] + right2_[j] * x_[j + 1];
        }
	}

	void FEM::mat(){
	    for (auto i = 0; i < ELEMENT; i++){
            // temporal 1
            diag1_[i] += DX * 2.0 / 6.0 / DT;
            diag1_[i + 1] += DX * 2.0 / 6.0 / DT;
            left1_[i + 1] += DX * 1.0 / 6.0 / DT;
            right1_[i] += DX * 1.0 / 6.0 / DT;

			// diffusion 1
		    diag1_[i] += THETA * DIFF / DX;
            diag1_[i + 1] += THETA * DIFF / DX; 
		    left1_[i + 1] -= THETA * DIFF / DX;
            right1_[i] -= THETA * DIFF / DX;

            // temporal 2
            diag2_[i] += DX * 2.0 / 6.0 / DT;
            diag2_[i + 1] += DX * 2.0 / 6.0 / DT;
            left2_[i + 1] += DX * 1.0 / 6.0 / DT;
            right2_[i] += DX * 1.0 / 6.0 / DT;

			// diffusion 2
		    diag2_[i] -= (1.0 - THETA) * DIFF / DX;
			diag2_[i + 1] -= (1.0 - THETA) * DIFF / DX;
		    left2_[i + 1] += (1.0 - THETA) * DIFF / DX; 
            right2_[i] += (1.0 - THETA) * DIFF / DX;
        }
	}

	void FEM::tdma(){
		myvec p(NODE, 0.0), q(NODE, 0.0);

		p[0] = -right1_[0] / diag1_[0];
		q[0] = bound_[0] / diag1_[0];

		for (auto i = 1; i < NODE; i++){
		    p[i] = -right1_[i] / (diag1_[i] + left1_[i] * p[i - 1]);
		    q[i] = (bound_[i] - left1_[i] * q[i - 1]) / (diag1_[i] + left1_[i] * p[i - 1]);
	    }

		x_[NODE - 1] = q[NODE - 1];

		for (auto i = NODE - 2; i >= 0; i--){
			x_[i] = p[i] * x_[i + 1] + q[i];
		}
 	}
}

出力ファイルdata_nonste_diff.txtからデータを読み込み, gnuplotを用いてグラフを描き, nonste_diff.pngに保存する.

set title 'non-steady diffusion eq'

set xl 'x'
set yl 'u'
set grid

plot 'data_nonste_diff_0.txt'       using 1:2 w l title 't = 0'
replot 'data_nonste_diff_1000.txt'  using 1:2 w l title 't = 1'
replot 'data_nonste_diff_3000.txt'  using 1:2 w l title 't = 3'
replot 'data_nonste_diff_5000.txt'  using 1:2 w l title 't = 5'
replot 'data_nonste_diff_7000.txt'  using 1:2 w l title 't = 7'
replot 'data_nonste_diff_9000.txt'  using 1:2 w l title 't = 9'
replot 'data_nonste_diff_10000.txt' using 1:2 w l title 't = 10'

set terminal pngcairo
set output 'nonste_diff.png'
replot

nonste_diff.pngは次のようになる.

nonste_diff

1次元非定常移流拡散方程式

移流速度$c$で流れ, 拡散係数$D$で拡散する場合の移流拡散方程式は

\frac{\partial u}{\partial t} + \Delta \cdot (cu) 
= \Delta \cdot (D \Delta u)

である.
$1$次元においては

\frac{\partial u}{\partial t} + c \frac{\partial u}{\partial x} 
= D \frac{\partial^2 u}{\partial t^2}

となる.

#include <algorithm> // for std::min
#include <array>     // for std::array
#include <fstream>   // for std::ofstream
#include <iostream>  // for std::endl
#include <sstream>   // for std::string, std::stringstream
#include <vector>    // fos std::vector
#include <boost/assert.hpp> // for BOOST_ASSERT
#include <boost/format.hpp> // for boost::format

namespace fem{
    class FEM final{
		public:
			using myvec = std::vector<double>;
	
    	private:
        	enum class boundary_condi_type{
            	DIRICLET = 0,
            	LEFT_NEUMANN = 1,
            	RIGHT_NEUMANN = 2
        	};

            // DIRICLET or LEFT NEUMANN or RIGHT NEUMANN
        	static auto constexpr BCT = boundary_condi_type::DIRICLET;

			// boundary condition
        	static auto constexpr D0 = 0.0; // left Dirichlet
    		static auto constexpr D1 = 0.0; // right Direchlet
			static auto constexpr N0 = 1.0; // left Neumann
	    	static auto constexpr N1 = 0.5; // right Neumann

            // coefficient
			static auto constexpr C = 0.1;     // advection
            static auto constexpr DIFF = 0.01; // diffusion 
            static auto constexpr THETA = 0.5; // theta method

            // for discrete of x and t
 	    	static auto constexpr ELEMENT = 100; // mesh number
        	static auto constexpr LENGTH = 1.0;  // xrange
            static auto constexpr TEND = 10.0;   // trange
    
    	public:
			static auto constexpr TLOOP = 10000;
            static auto constexpr DT = TEND / TLOOP;
    		static auto constexpr TREP = TLOOP / 1000;
			static auto constexpr NODE = ELEMENT + 1;
			static auto constexpr DX = LENGTH / ELEMENT;
			static auto constexpr KAP = DIFF * DT / (DX * DX);
	
    	private:
        	myvec bound_;
        	myvec diag1_;
			myvec diag2_;
	    	myvec left1_;
			myvec left2_;
	    	myvec right1_;
	    	myvec right2_;
			myvec x_;

    	public:
        	FEM()
            	: bound_(NODE, 0.0), diag1_(NODE, 0.0), diag2_(NODE, 0.0), 
                  left1_(NODE, 0.0), left2_(NODE, 0.0), right1_(NODE, 0.0), right2_(NODE, 0.0), x_(NODE, 0.0)
        	{
				for (auto i = 0; i < NODE; i++){
					x_[i] = std::min(DX * static_cast<double>(i), 1.0 - DX * static_cast<double>(i)); // f = 
				}
			}

        	~FEM() = default;

			// copy constructorでcopy禁止
        	FEM(FEM const &dummy) = delete;

			// operator=()でもcopy禁止
        	FEM & operator=(FEM const &dummy) = delete;

			// output for each TLOOP
			bool result_output(int i);

			// boundary condition
        	void boundary();
			void boundary2();

			// TDMA method
	    	void tdma();

			// make stiffness matrix
	    	void mat();
    };
}

int main(){
    // KAP > 0.5のとき強制終了 
    // Neumannの安定性解析より, KAP <= 0.5でないと正しい結果が得られない
    BOOST_ASSERT(fem::FEM::KAP <= 0.5);

    fem::FEM fem_obj;

	fem_obj.result_output(0);

	if (!fem_obj.result_output(0)){
		std::cerr << "output file not open" << std::endl;
        return -1;
	}

	fem_obj.mat();
	fem_obj.boundary();

	for (auto i = 1; i <= fem::FEM::TLOOP; i++){
		fem_obj.boundary2();
		fem_obj.tdma();

		if (!(i % 1000)){
			if (!fem_obj.result_output(i)){
				std::cerr << "output file not open" << std::endl;
        		return -1;
			}
		}
	}

	return 0;
}

namespace fem{
	bool FEM::result_output(int i){
		std::stringstream ss;
		std::string name;
		std::ofstream ofs;

		ss << i;
		name = ss.str();
		name = "data_nonste_advec_diff_" + name + ".txt";
		
		ofs.open(name.c_str());
		if (!ofs){
			return false;
		}

		for (auto j = 0; j < NODE; j++){
			ofs << DX * static_cast<double>(j) << " " << x_[j] << std::endl;
		}

		return true;
	}

    void FEM::boundary(){
		switch (BCT){
            case boundary_condi_type::DIRICLET:
            	diag1_[0] = 1.0;
				diag1_[NODE - 1] = 1.0;
                diag2_[0] = 1.0;
                diag2_[NODE - 1] = 1.0;
			    left1_[NODE - 1] = 0.0;
                left2_[NODE - 1] = 0.0;
        	    right1_[0] = 0.0;
                right2_[0] = 0.0;
                break;
		
            case boundary_condi_type::LEFT_NEUMANN:
        	    diag1_[NODE - 1] = 1.0;
                diag2_[NODE - 1] = 1.0;
				left1_[NODE - 1] = 0.0;
                left2_[NODE - 1] = 0.0;
                break;
			
            case boundary_condi_type::RIGHT_NEUMANN:
			    diag1_[0] = 1.0;
                diag2_[0] = 1.0;
        	    right1_[0] = 0.0;
                right2_[0] = 0.0;
                break;
            
            default:
                BOOST_ASSERT(!"error! wrong boundary condition");
                break;
        }
	}

    void FEM::boundary2(){
		switch (BCT){
            case boundary_condi_type::DIRICLET:
                bound_[0] = D0;
        	    bound_[NODE - 1] = D1;
                break;
		
            case boundary_condi_type::LEFT_NEUMANN:
                bound_[0] = diag2_[0] * x_[0] + right2_[0] * x_[1] - N0 * DIFF;
				bound_[NODE - 1] = D1;
                break;
			
            case boundary_condi_type::RIGHT_NEUMANN:
                bound_[0] = D0;  
				bound_[NODE - 1] = left2_[NODE - 1] * x_[NODE - 2] + left2_[NODE - 1] * x_[NODE - 1] + N1 * DIFF;
                break;
            
            default:
                BOOST_ASSERT(!"error! wrong boundary condition");
                break;
        }

		for (auto j = 1; j < NODE - 1; j++){
            bound_[j] = left2_[j] * x_[j - 1] + diag2_[j] * x_[j] + right2_[j] * x_[j + 1];
        }
	}

	void FEM::mat(){
	    for (auto i = 0; i < ELEMENT; i++){
            // temporal 1
            diag1_[i] += DX * 2.0 / 6.0 / DT;
            diag1_[i + 1] += DX * 2.0 / 6.0 / DT;
            left1_[i + 1] += DX * 1.0 / 6.0 / DT;
            right1_[i] += DX * 1.0 / 6.0 / DT;

			//advection 1
			diag1_[i] -= THETA * C / 2.0;
			diag1_[i + 1] += THETA * C / 2.0;
			left1_[i + 1] -= THETA * C / 2.0;
			right1_[i] += THETA * C / 2.0;

			// diffusion 1
		    diag1_[i] += THETA * DIFF / DX;
            diag1_[i + 1] += THETA * DIFF / DX; 
		    left1_[i + 1] -= THETA * DIFF / DX;
            right1_[i] -= THETA * DIFF / DX;

            // temporal 2
            diag2_[i] += DX * 2.0 / 6.0 / DT;
            diag2_[i + 1] += DX * 2.0 / 6.0 / DT;
            left2_[i + 1] += DX * 1.0 / 6.0 / DT;
            right2_[i] += DX * 1.0 / 6.0 / DT;

			//advection 2
			diag2_[i] += (1 - THETA) * C / 2.0;
			diag2_[i + 1] -= (1 - THETA) * C / 2.0;
			left2_[i + 1] += (1 - THETA) * C / 2.0;
			right2_[i] -= (1 - THETA) * C / 2.0;

			// diffusion 2
		    diag2_[i] -= (1.0 - THETA) * DIFF / DX;
			diag2_[i + 1] -= (1.0 - THETA) * DIFF / DX;
		    left2_[i + 1] += (1.0 - THETA) * DIFF / DX; 
            right2_[i] += (1.0 - THETA) * DIFF / DX;
        }
	}

	void FEM::tdma(){
		myvec p(NODE, 0.0), q(NODE, 0.0);

		p[0] = -right1_[0] / diag1_[0];
		q[0] = bound_[0] / diag1_[0];

		for (auto i = 1; i < NODE; i++){
		    p[i] = -right1_[i] / (diag1_[i] + left1_[i] * p[i - 1]);
		    q[i] = (bound_[i] - left1_[i] * q[i - 1]) / (diag1_[i] + left1_[i] * p[i - 1]);
	    }

		x_[NODE - 1] = q[NODE - 1];

		for (auto i = NODE - 2; i >= 0; i--){
			x_[i] = p[i] * x_[i + 1] + q[i];
		}
 	}
}

出力ファイルdata_nonste_advec_diff.txtからデータを読み込み, gnuplotを用いてグラフを描き, nonste_advec_diff.pngに保存する.

set title 'non-steady advection diffusion eq'

set xl 'x'
set yl 'u'
set grid

plot 'data_nonste_advec_diff_0.txt'       using 1:2 w l title 't = 0'
replot 'data_nonste_advec_diff_1000.txt'  using 1:2 w l title 't = 1'
replot 'data_nonste_advec_diff_3000.txt'  using 1:2 w l title 't = 3'
replot 'data_nonste_advec_diff_5000.txt'  using 1:2 w l title 't = 5'
replot 'data_nonste_advec_diff_8000.txt'  using 1:2 w l title 't = 7'
replot 'data_nonste_advec_diff_10000.txt' using 1:2 w l title 't = 10'

set terminal pngcairo
set output 'nonste_advec_diff.png'
replot

nonste_advec_diff.pngは次のようになる.

nonste_advec_diff

1次元Burgers方程式

1次元のBurgers方程式は

\frac{\partial u}{\partial t} + u \frac{\partial u}{\partial x} 
= D \frac{\partial^2 u}{\partial t^2}

となる.
左辺第2項目が非線形項であるが, Picard反復を用いて線形化する.
理論的にはHopf-Cole変換によって拡散方程式に帰着させることができる.

その他の解法

(有限)差分法や有限要素法のほかにも有限体積法がある.

参考文献

全体を通して

連立一次方程式に関して

常微分方程式に関して

偏微分方程式に関して

Eigenに関して

boostに関して

gnuplotに関して

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8