笔记-1

One-Dimensional Transient Heat Conduction: A Solution with Finite Difference Algorithm

MATLAB实现

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 1D transient heat conduction  %
% space discretization: finite- %
%  difference time integration: %
%       Explicit Euler          %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%-- get initial wall time:
time0 = clock();

%-- Simulation cell parameters:

Nx = 128; % 定义网格数
dx = 1.0; % 空间步长

%-- Time integration parameters:

nstep = 600; % 时间步
nprint = 200; % 绘图间隔
dtime = 0.2; % 时间步长

%-- Initialize temperature filed & grid:初始场

for i = 1:Nx
    u0(i) = 0.0;
    x(i) = i*dx;

%-- 定义初始热区

    if((i>=44) && (i<=84))
        u0(i) = 1.0;
    end
end

%-- 时间推进-显式欧拉
%-- Evolve temperature field:
%--

ncount = 0;
for istep = 1:nstep

    for i = 2:Nx-1 % 边界i=1,i=Nx没有更新,相当于固定边界
        u0(i) = u0(i)+dtime*(u0(i+1)-2.0*u0(i)+u0(i-1))...
            /(dx*dx)
    end

%-- Display results:

if((mod(istep,nprint) == 0) || (istep == 1)) 
% istep除nprint余数为0,即istep为了print整数 或 istep == 1即初始

    ncount = ncount+1;
    subplot(2,2,ncount);
    plot(x,u0);
    time=sprintf('%d',istep);
    title(['time' time]);
    axis([0 Nx -0.5 1.5]);
    xlabel('x');
    ylabel('Temperature');

end %if
end %istep

compute_time = etime(clock(),time0);
fprintf('Compute Time: %5d\n',compute_time);

运行结果:

Python实现

import numpy as np
import matplotlib.pyplot as plt
import time

# 获取初始时间
time0 = time.time() # 获取当前时间戳

# 模拟单元参数
Nx = 128
dx = 1.0

# 时间积分参数
nstep = 600
nprint = 200
dtime = 0.2

# 初始化温度场和网格
u0 = np.zeros(Nx)   # 创建一个长度为Nx的全零数组,储存温度值
x = np.arange(1, Nx+1)*dx   # 创建空间坐标数组,从dx到Nx*dx,步长dx;np.arange(1,Nx+1)生成1到Nx+1的整数序列,乘以dx得到实际空间坐标

# 定义初始热区
u0[43:84] = 1.0 # 将索引43到83的温度设为1.0注意不包括84

# 时间推进-显式欧拉法
ncount = 0
for istep in range(1, nstep+1):
    u0[1:-1] = u0[1:-1]+dtime*(u0[2:]-2.0*u0[1:-1]+u0[:-2]/(dx*dx))
    # u0[1:-1]:表示除了第一个和最后一个点(边界)之外的所有点
    # u0[2:]:表示从第二个点到最后一个点,比u0[1:-1]超前一个位置
    # u0[:-2]:表示从第一个点到倒数第二个点(比 u0 [1:-1] 滞后一个位置)
    if istep % nprint == 0 or istep == 1:
        ncount += 1
        plt.subplot(2,2,ncount)
        plt.plot(x,u0)
        plt.title(f'time {istep}')
        
        plt.axis([0,Nx,-0.5,1.5])
        plt.xlabel('x')
        plt.ylabel('Temperature')

compute_time = time.time()-time0
print(f'Compute Time: {compute_time:.2f} seconds')

plt.tight_layout()
plt.show()

运行结果:

Simulation of the spinodal decomposition of a binary alloy with explict Euler finite-difference algorithm

MATLAB实现

fd_ch_v1.m

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                               %
%FINEITE-DIFFIRENCE PHASE-FIELD %
%       CODE FOR SOLVING        %
%     CAHN-HILLIARD EQUATION    %
%                               %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% get intial wall time:
% 获取初始wall时间
time0 = clock();
format long;

out2 = fopen('time_energ.out','w'); % 打开一个文件用于写入时间和能量数据

%% Simulation cell parameters:
% 模拟单元格参数

Nx = 64;
Ny = 64;
NxNy = Nx*Ny;
dx = 1.0;
dy = 1.0;

%% Time integration parameters:

nstep = 10000;
nprint = 50;
dtime = 0.01;
ttime = 0.0;

%% Material specific Parameters:

c0 = 0.40;
mobility = 1.0;
grad_coef = 0.5;

%% prepare microstructure:

iflag = 1;
[con] = micro_ch_pre(Nx,Ny,c0,iflag); % 调用函数生成初始浓度场

%% Evolve


for istep = 1:nstep

    ttime  = ttime+dtime;

    for i = 1:Nx
        for j = 1:Ny

            % 定义相邻网格点(用于周期性边界条件)
            jp = j+1;
            jm = j-1;

            ip = i+1;
            im = i-1;

            jp = j+1;
            jm = j-1;

            ip = i+1;
            im = i-1;

            % 应用周期性边界条件
            if(im == 0)
                im = Nx; % 左边界外的点等于右边界点
            end
            if(ip == (Nx+1))
                ip = 1; % 右边界外的点等于左边界点
            end

            if(jm == 0)
                jm = Ny; % 下边界外的点等于上边界点
            end

            if(jp == (Ny+1))
                jp = 1; % 上边界外的点等于下边界点
            end

            % 获取当前点和相邻点的浓度值
            hne = con(ip,j); % 右邻点
            hnw = con(im,j); % 左邻点
            hns = con(i,jm); % 下邻点
            hnn = con(i,jp); % 上邻点
            hnc = con(i,j); % 当前点

            % 计算浓度场的拉普拉斯(二阶导数)
            lap_con(i,j) = (hnw+hne+hns+hnn-4.0*hnc)/(dx*dy);


%% derivative of free energy:

            [dfdcon] = free_energ_ch_v1(i,j,con);
            % 调用函数计算自由能导数

            dummy(i,j) = dfdcon-grad_coef*lap_con(i,j);
             % 计算Cahn-Hilliard方程中的一项:自由能导数减去梯度项

        end % for j
    end % for i

%%

    for i = 1:Nx
        for j = 1:Ny

            jp = j+1;
            jm = j-1;

            ip = i+1;
            im = i -1;

            jp = j+1;
            jm = j-1;

            ip = i+1;
            im = i-1;

            if(im == 0)
                im = Nx;
            end
            if(ip == (Nx+1))
                ip = 1;
            end

            if(jm == 0)
                jm = Ny;
            end

            if(jp == (Ny+1))
                jp = 1;
            end

            hne = con(ip,j);
            hnw = con(im,j);
            hns = con(i,jm);
            hnn = con(i,jp);
            hnc = con(i,j);

            lap_dummy(i,j) = (hnw+hne+hns+hnn-4.0*hnc)/(dx*dy);

%% time integration:
            % Cahn-Hilliard方程:dc/dt = M * ∇²(δF/δc - κ∇²c)
            con(i,j) = con(i,j)+dtime*mobility*lap_dummy(i,j);

%% for small deviations:
% 限制浓度值在合理范围内

            if(con(i,j) >= 0.9999)
                con(i,j) = 0.9999;
            end
            if(con(i,j) < 0.00001)
                con(i,j) = 0.00001;
            end

        end % j
    end % i

%% print results

    if((mod(istep,nprint) == 0 || (istep == 1)))

        fprintf('done step: %5d\n',istep);

        %fname1 = sprintf('time_%d.out',istep);
        %out1 = fopen(fname1,"w");

        %for i = 1:Nx
            %for j = 1:Ny
                %fprintf(out1,'%5d %5d %14.6e\n',i,j,con(i,j));
            %end
        %end

        %fclose(out1);

%% write vtk file:

%% calculate total energy

        [energ] = calculate_energ(Nx,Ny,con,grad_coef);

        fprintf(out2,'%14e %14.6e\n',ttime,con,grad_coef);
        
        write_vtk_grid_values(Nx,Ny,dx,dy,istep,con);

    end % if
end % istep

%% calculate compute time:

compute_time = etime(clock(),time0);
fprintf('Compute Time: %10d\n',compute_time);

micro_ch_pre.m

% 初始条件生成
function[con] = micro_ch_pre(Nx,Ny,c0,iflag)

format long;

NxNy = Nx*Ny;

noise = 0.02;

if(iflag == 1) % 2D
    for i = 1:Nx
        for j = 1:Ny
            con(i,j) = c0+noise*(0.5-rand); % rand用于生成[0,1)的均匀随机数
        end
    end

else % 1D
    con = zeros(NxNy,1);

    for i = 1:Nx
        for j = 1:Ny
            ii = (i-1)*Nx+j; % 2D网格映射为1D下标
            con(ii) = c0+noise*(0.5-rand);
        end
    end

end % if

end % endfunction

calculate_energ.m

function [energ] = calculate_energ(Nx,Ny,con,grad_coef)

format long;

energ =0.0;

for i=1:Nx-1
    ip = i + 1;
    for j=1:Ny-1 
        jp = j + 1;
        
        energ = energ + con(i,j)^2*(1.0-con(i,j))^2 + ...
  0.5*grad_coef*((con(ip,j)-con(i,j))^2 + (con(i,jp)-con(i,j))^2);
    end
end

end %endfunction

free_energ_ch.m

% 自由能密度对浓度的导数
function [dfdcon] =free_energ_ch_v1(i,j,con)

format long;

A=1.0;

dfdcon =A*(2.0*con(i,j)*(1-con(i,j))^2-2.0*con(i,j)^2 * (1.0-con(i,j)));
% f(c)=A*c^2*(1-c)^2
% f'(c)=A*[2*c*(1-c)^2+c^2*(-2)*(1-c)]=A*[2*c*(1-c)^2-2*c^2*(1-c)]

end %endfunction

write_vtk_grid_values.m

function [  ]= write_vtk_grid_values(nx,ny,dx,dy,istep,data1)     % [ ]表示无返回    

format long

%-- open output file

fname=sprintf('time_%d.vtk',istep);
out =fopen(fname,'w'); % 'w'覆盖写入

nz=1;

npoin =nx*ny*nz;

% start writing ASCII VTK file:

% header of VTK file
% VTK文件文件头写入
fprintf(out,'# vtk DataFile Version 2.0\n');
fprintf(out,'time_10.vtk\n');
fprintf(out,'ASCII\n');
fprintf(out,'DATASET STRUCTURED_GRID\n');

%--- coords of grid points:

fprintf(out,'DIMENSIONS %5d  %5d  %5d\n',nx,ny,nz); % 声明网格维度
fprintf(out,'POINTS %7d   float\n',npoin); % 声明节点总数

for i = 1:nx
    for j = 1:ny
        
        x =(i-1)*dx;
        y =(j-1)*dy;
        z = 0.0;
        
        fprintf(out, '%14.6e   %14.6e  %14.6e\n',x,y,z); % 14.6e输出为14字符的6位小数的科学计数法
    end
end

%--- write grid point values:

fprintf(out,'POINT_DATA %5d\n',npoin);

fprintf(out,'SCALARS CON  float  1\n'); % 数据组件数 1表示标量,3表示矢量

fprintf(out,'LOOKUP_TABLE default\n'); % 颜色映射设为默认

for i = 1:nx
    for j = 1:ny
        ii=(i-1)*nx+j;
        
        fprintf(out,'%14.6e\n',data1(i,j));
    
    end
end

fclose(out);

end %endfunction

laplacian.m

function [grad] =laplacian(nx,ny,dx,dy)

format long;

nxny=nx*ny;

r=zeros(1,nx);
r(1:2)=[2,-1]; % 定义r(1)=2, r(2)=-1, 其余为0
T=toeplitz(r); % Toeplitz矩阵,对角元素相等

E=speye(nx); % 生成稀疏单位矩阵(仅对角为1,其余为0)

grad=-(kron(T,E)+kron(E,T));
% 克罗内克积 kron(T,E)为x方向拉普拉斯矩阵2D拓展 kron(E,T)为y方向拉普拉斯矩阵2D拓展

%-- for periodic boundaries
% 添加周期性边界条件
for i=1:nx
    ii=(i-1)*nx+1;
    jj=ii+nx-1;
    grad(ii,jj)=1.0; % 定义左边界的右临点为右边界
    grad(jj,ii)=1.0; % 定义右边界的左临点为左边界

    kk=nxny-nx+i;
    grad(i,kk)=1.0;
    grad(kk,i)=1.0;
end

grad = grad /(dx*dy); % 除以网格步长乘积以修正导数量纲

end %endfunction

Python实现

fd_cd_v1.py

import numpy as np
import time
from calculate_energ import calculate_energ
from laplacian import laplacian
from micro_ch_pre import micro_ch_pre
from write_vtk_grid_values import write_vtk_grid_values
from free_energ_ch_v1 import free_energ_ch_v1

def main():
    time0 = time.time()	# time.time()返回当前时间戳
    out2 = open('time_energ.out','w')

    Nx = 64
    Ny = 64
    NxNy = Nx * Ny
    dx = 1.0
    dy = 1.0

    nstep = 10000
    nprint = 50
    dtime = 0.01
    ttime = 0.0

    c0 = 0.40
    mobility = 1.0
    grad_coef = 0.5

    iflag = 1
    con = micro_ch_pre(Nx,Ny,c0,iflag)

    lap_con = np.zeros((Nx,Ny))	# 储存浓度差的拉普拉斯结果
    dummy = np.zeros((Nx,Ny))	# 临时数组
    lap_dummy = np.zeros((Nx,Ny))	#储存临时数组的拉普拉斯结果

    for istep in range(1,nstep+1):		# 时间循环,从1到nstep(包含nstep)
        ttime = ttime + dtime
	
	# 计算浓度场的拉普拉斯和自由能导数
	# 双重循环遍历所有网格点
        for i in range(Nx):
            for j in range(Ny):

                jp = j + 1
                jm = j - 1

                ip = i + 1
                im = i - 1

                if im == -1:	# 左边界外
                    im = Nx - 1	# 循环到右边界
                if ip == Nx:	# 右边界外
                    ip = 0		#循环到左边界
                
                if jm == -1:	# 下边界外
                    jm = Ny - 1	# 循环到上边界
                if jp == Ny:	# 上边界外
                    jp = 0		# 循环到下边界

                hne = con[ip,j]	# 右临点
                hnw = con[im,j]	# 左临点
                hns = con[i,jm]	# 下临点
                hnn = con[i,jp]	# 上临点
                hnc = con[i,j]	# 当前点

                lap_con[i,j] = (hnw + hne + hns + hnn - 4.0 * hnc) / (dx * dy)
		# 计算拉普拉斯算子(二阶导数近似)

                dfdcon = free_energ_ch_v1(i,j,con)
		# 计算自由能对浓度的导数

                dummy[i,j] = dfdcon - grad_coef * lap_con[i,j]
		# 计算临时变量dummy        
	
	# 计算dummy的拉普拉斯并更新浓度场
        for i in range(Nx):
            for j in range(Ny):

                jp = j + 1
                jm = j - 1

                ip = i + 1
                im = i - 1

                if im == -1:
                    im = Nx - 1
                if ip == Nx:
                    ip = 0
                
                if jm == -1:
                    jm = Ny - 1
                if jp == Ny:
                    jp = 0

                hne = dummy[ip,j]
                hnw = dummy[im,j]
                hns = dummy[i,jm]
                hnn = dummy[i,jp]
                hnc = dummy[i,j]

                lap_dummy[i,j] = (hnw + hne + hns + hnn - 4.0 * hnc) / (dx * dy)
		
		# 更新浓度场(Cahn-Hilliard方程的时间推进)
                con[i,j] = con[i,j] + dtime * mobility * lap_dummy[i,j]

                if con[i,j] >= 0.9999:
                    con[i,j] = 0.9999
                if con[i,j] < 0.00001:
                    con[i,j] = 0.00001

        if(istep % nprint == 0) or (istep == 1):

            print(f'done step: {istep:5d}')

            energ = calculate_energ(Nx,Ny,con,grad_coef)

            out2.write(f'{ttime:14e} {energ:14.6e}\n')

            write_vtk_grid_values(Nx,Ny,dx,dy,istep,con)

    compute_time = time.time() - time0
    print(f'Compute Time: {compute_time:10.0f}')

    out2.close

if __name__ == "__main__":
    main()

micro_ch_pre.py

import numpy as np

def micro_ch_pre(Nx,Ny,c0,iflag):
    NxNy = Nx * Ny
    noise = 0.02

    if iflag == 1:
        con = np.zeros((Nx,Ny))

        for i in range(Nx):
            for j in range(Ny):
		# 初始化浓度:平均浓度c0加上随机扰动
		# np.random.rand()生成[0,1)之间的随机数
		# (0.5 - np.random.rand())使随机扰动在[-0.5, 0.5)之间
		# 乘以noise后,扰动范围变为[-0.01, 0.01)
                con[i,j] = c0 + noise * (0.5 - np.random.rand())

    else:
	# 如果iflag不等于1,则创建一个长度为NxNy的一维零数组
        con = np.zeros(NxNy)

        for i in range(Nx):
            for j in range(Ny):
                ii = i * Nx + j
                con[ii] = c0 + noise * (0.5 - np.random.rand())

    return con

calculate_energ.py

def calculate_energ(Nx,Ny,con,grad_coef):
    energ = 0.0

    for i in range(Nx-1):
        ip = i+1
        for j in range(Ny-1):
            jp = j+1

            energ = (energ + con[i,j]**2 * (1.0 - con[i,j])**2 + 0.5 * grad_coef * ((con[ip,j] - con[i,j])**2 + (con[i,jp] - con[i,j])**2))
            
            return energ

free_energ_ch.py

def free_energ_ch_v1(i,j,con):
    c = con[i,j]
    dfdcon = 2 * c * (1-c) * (1 - 2*c)
    return dfdcon

write_vtk_grid_values.py

def write_vtk_grid_values(nx,ny,dx,dy,istep,data1):
    fname = f'time_{istep}.vtk'
    out = open(fname,'w')

    nz = 1
    npoin = nx * ny * nz
    
    out.write('# vtk DataFile Version 2.0\n')
    out.write(f'time_{istep}.vtk\n')
    out.write('ASCII\n')
    out.write('DATASET STRUCTURED_GRID\n')

    out.write(f'DIMENSIONS {nx:5d} {ny:5d} {nz:5d}\n')
    out.write(f'POINTS {npoin:7d} float\n')

    for i in range(nx):
        for j in range(ny):
            x = i * dx
            y = j * dy
            z = 0.0

            out.write(f'{x:14.6e} {y:14.6e} {z:14.6e}\n')

    out.write(f'POINT_DATA {npoin:5d}\n')
    out.write('SCALARS CON  float  1\n')
    out.write('LOOKUP_TABLE default\n')

    for i in range(nx):
        for j in range(ny):
            out.write(f'{data1[i,j]:14.6e}\n')

    out.close()

laplacian.py

import numpy as np
import scipy.sparse as sp	# 导入scipy稀疏矩阵模块
from scipy.sparse import kron, eye, diags	# 从scipy导入特定的稀疏矩阵函数

def laplacian(nx,ny,dx,dy):
    nxny = nx * ny

    r = np.zeros(nx)
    r[0] = 2
    r[1] = -1

    T = sp.lil_matrix((nx,nx))
    # 创建nx×nx的稀疏矩阵(LIL格式,便于修改)
    # 填充T矩阵,构造1D拉普拉斯算子(非周期性)
    for i in range(nx):
        for j in range(nx):
            if i == j:
                T[i,j] = 2	# 对角线元素
            elif abs(i - j) == 1:
                T[i,j] = -1	# 相邻元素
   
    # 将T转换为CSR格式(便于高效的矩阵运算)
    T = T.tocsr()
    # 创建nx×nx的单位矩阵(CSR格式)
    E = eye(nx, format='csr')

    # 使用Kronecker积构造2D拉普拉斯算子
    # kron(T, E) 对应x方向的二阶导数
    # kron(E, T) 对应y方向的二阶导数(原代码中E.T应为笔误,E是方阵转置后不变)
    grad = -(kron(T,E) + kron(E,T))
    
    # 将grad转换为LIL格式,便于修改边界条件
    grad = grad.tolil()

    # 处理周期性边界条件

    for i in range(nx):
        ii = i * nx
        jj = ii + nx -1
        grad[ii,jj] = 1.0
        grad[jj,ii] = 1.0

    # 转换回CSR格式以提高后续运算效率
    grad = grad.tocsr()
    grad = grad / (dx * dy)

    return grad

# LIL 格式:适合构建矩阵(如循环填充元素)或随机修改元素,但不适合大规模数值运算。
# CSR 格式:适合矩阵乘法、切片、与向量的乘积等数值操作,运算效率极高,但修改元素不方便。