importnumpyasnpfromfractionsimportFractionfromcollectionsimportdefaultdictdefinverse_matrix(matrix):# 将矩阵元素转换为浮点数以计算逆矩阵matrix_float=matrix.astype(float)inverse=np.linalg.inv(matrix_float)# 将逆矩阵元素转换为分数inverse_fraction=[[Fraction(inverse[i,j]).limit_denominator()forjinrange(len(inverse))]foriinrange(len(inverse))]returninverse_fractiondefprint_matrix_fraction(matrix,is_column_vector=False):""" 支持一维向量和二维矩阵的分数字符串打印 :param matrix: 一维列表(向量)或二维列表/数组(矩阵) :param is_column_vector: 一维向量是否按列向量格式打印(默认False:行向量) """# 步骤1:统一转换为二维矩阵格式ifisinstance(matrix,(list,np.ndarray)):# 若为一维,转为二维(行向量:1×N 或 列向量:N×1)ifnp.ndim(matrix)==1:ifis_column_vector:# 列向量:N行1列two_d_matrix=[[x]forxinmatrix]else:# 行向量:1行N列two_d_matrix=[matrix]else:# 若为二维,直接使用two_d_matrix=matrixelse:raiseTypeError("输入必须是列表或numpy数组")# 步骤2:转换为Fraction数组fraction_matrix=np.array([[Fraction(x).limit_denominator()forxinrow]forrowintwo_d_matrix])rows=len(fraction_matrix)cols=len(fraction_matrix[0])# 步骤3:转换为字符串矩阵并计算每列最大宽度str_matrix=[]col_widths=[0]*cols# 每列的最大宽度forrowinfraction_matrix:str_row=[]forj,finenumerate(row):s=f"{f.numerator}/{f.denominator}"str_row.append(s)current_length=len(s)ifcurrent_length>col_widths[j]:col_widths[j]=current_lengthstr_matrix.append(str_row)# 步骤4:打印(保持原有对齐风格)foriinrange(rows):row_elements=[]forjinrange(cols):element=str_matrix[i][j]formatted_element=f"{element:>{col_widths[j]}}"# 右对齐ifj<cols-1:formatted_element+=", "else:formatted_element+=" "row_elements.append(formatted_element)formatted_row="".join(row_elements)print(f"[ {formatted_row}]")print()defintegral_xi(x,j):return(x**(j+1))/(j+1)defcompute_coef(x,k):y=[]forjinrange(k):var=x**jy.append(var)returnydefid_tostring(rj):mystr=str(rj)ifrj==0:mystr=' 'ifrj>0:mystr='+'+str(rj)returnmystrdefcoef_tostring(coef,i):mystr=str(coef)ifcoef>=0:ifi==0:mystr=' '+mystrelse:mystr='+'+mystrreturnmystrdefcoef_toabsstring(coef):abs_str=str(abs(coef))s='+'ifcoef<0:s='-'returnabs_str,sdefcal_polynomial_matrix(r,kk):arrays_list=[]forminrange(kk):j=-r+mxia=Fraction(j)-Fraction(1,2)xib=Fraction(j)+Fraction(1,2)a_list=[]foriinrange(kk):val=integral_xi(xib,i)-integral_xi(xia,i)a_list.append(val)arrays_list.append(a_list)matrix=np.vstack(arrays_list)returnmatrixdefcal_polynomial_coefficients(r,kk,xfrac):matrix=cal_polynomial_matrix(r,kk)inverse=inverse_matrix(matrix)xv=compute_coef(xfrac,kk)yv=np.dot(xv,inverse)returnyvdefcalc_coef_formula(kk,xfrac):rows_list=[]forrinrange(kk):#-r+lym=cal_polynomial_coefficients(r,kk,xfrac)rows_list.append(ym)returnnp.vstack(rows_list)defbuild_moment_matrix(template_index:int,stencil_width:int)->np.ndarray:r""" Build the moment matrix M for a given substencil, where M @ poly_coeffs = cell_averages The substencil corresponding to `template_index = r` uses the cells: $$ I_{i - r},\ I_{i - r + 1},\ \dots,\ I_{i - r + k - 1} $$ with $k = \text{stencil\_width}$. Each cell $I_j$ is the interval $[j - 1/2, j + 1/2]$. The matrix entry M[m, i] is the integral of the monomial $\xi^i$ over the m-th cell in the substencil (i.e., over $I_{j_m}$ where $j_m = i - r + m$): $$ M[m, i] = \int_{j_m - 1/2}^{j_m + 1/2} \xi^i \, d\xi $$ Parameters ---------- template_index : int Index of the substencil (r = 0, 1, ..., k-1). Larger values shift the stencil left. stencil_width : int Number of cells in the substencil (k). Returns ------- M : np.ndarray of shape (k, k) Moment matrix with exact fractional entries. """rows=[]forminrange(stencil_width):# Spatial index of the m-th cell in the substencil: j = i - r + mj=-template_index+mleft=Fraction(j)-Fraction(1,2)right=Fraction(j)+Fraction(1,2)row=[]foriinrange(stencil_width):val=integral_xi(right,i)-integral_xi(left,i)row.append(val)rows.append(row)returnnp.array(rows,dtype=object)defcompute_stencil_coefficients_for_point(template_index:int,stencil_width:int,x_point:Fraction)->np.ndarray:r""" Compute the reconstruction coefficients for a single substencil used to approximate the point value at `x_point` (e.g., $x = i + 1/2$) from cell averages. The substencil corresponding to `template_index = r` (where $r = 0, 1, ..., k-1$) uses the following $k = \text{stencil\_width}$ consecutive cells: $$ I_{i - r},\ I_{i - r + 1},\ \dots,\ I_{i - r + k - 1} $$ For example, when `stencil_width = 3` and reconstructing $v_{i+1/2}^-$: - `template_index = 0` → cells [i, i+1, i+2] (rightmost) - `template_index = 1` → cells [i-1, i, i+1] (middle) - `template_index = 2` → cells [i-2, i-1, i ] (leftmost) The returned coefficients `c[0], c[1], ..., c[k-1]` satisfy: $$ p(x_{\text{point}}) = \sum_{j=0}^{k-1} c[j] \cdot \bar{v}_{i - r + j} $$ where $p(\cdot)$ is the unique polynomial of degree ≤ k−1 that matches the cell averages over the substencil. Parameters ---------- template_index : int Index of the substencil (0 ≤ template_index < stencil_width). Larger values shift the stencil further to the left. stencil_width : int Number of cells in the substencil (order of accuracy = stencil_width). x_point : Fraction Relative coordinate where the point value is reconstructed, e.g., Fraction(1, 2) for $i + 1/2$. Returns ------- coefficients : np.ndarray of shape (stencil_width,) Reconstruction coefficients for the cell averages in the substencil, ordered from leftmost to rightmost cell in the stencil. """M=build_moment_matrix(template_index,stencil_width)M_inv=inverse_matrix(M)monomials=np.array([x_point**iforiinrange(stencil_width)],dtype=object)coefficients=monomials@M_invreturncoefficientsdefcompute_optimal_reconstruction_stencil(stencil_width:int,x_point:Fraction)->np.ndarray:""" Compute the optimal (high-order) reconstruction stencil centered at cell i, using `stencil_width` consecutive cells symmetric around i. The stencil covers cells: [i - (k-1)//2, ..., i, ..., i + (k-1)//2] and reconstructs the point value at x = i + x_point. Example: k=5, x_point=1/2 → cells [i-2, i-1, i, i+1, i+2] Returns coefficients [c_{-2}, c_{-1}, c_0, c_1, c_2] """ifstencil_width%2==0:raiseValueError("Optimal stencil requires odd stencil_width for symmetry.")r=stencil_width//2coefficients=compute_stencil_coefficients_for_point(r,stencil_width,x_point)returncoefficientsdefgenerate_weno_substencils(stencil_width:int,x_point:Fraction)->np.ndarray:""" Generate all k = stencil_width substencils for reconstructing a point value at x_point. The returned matrix has shape (k, k), where: - Row r corresponds to the substencil that uses cells: [I_{i - r}, I_{i - r + 1}, ..., I_{i - r + k - 1}] which is the r-th candidate stencil counting from the RIGHTMOST (r=0) to the LEFTMOST (r=k-1) stencil. For example, when k=3 and reconstructing v_{i+1/2}^-: r=0 → cells [i, i+1, i+2] (rightmost) r=1 → cells [i-1, i, i+1] (middle) r=2 → cells [i-2, i-1, i ] (leftmost) """stencils=[]forrinrange(stencil_width):# r = 0 → rightmost stencil# r = stencil_width-1 → leftmost stencilcoef=compute_stencil_coefficients_for_point(r,stencil_width,x_point)stencils.append(coef)returnnp.vstack(stencils)defgenerate_left_stencils(stencil_width:int,offset:Fraction=Fraction(1,2)):"""生成左偏模板(用于 vi+1/2)"""returngenerate_weno_substencils(stencil_width,offset)defgenerate_right_stencils(stencil_width:int,offset:Fraction=Fraction(1,2)):"""生成右偏模板(用于 vi-1/2)"""returngenerate_weno_substencils(stencil_width,-offset)defcal_iplus_index(jstart,cols):var_rj_list=[]forjinrange(cols):rj=jstart+jvar_rj=id_tostring(rj)var_rj_list.append(var_rj)returnvar_rj_listdefformat_signed_coef(coef,isFirstElement,width):""" vi+1/2(-),0= 1/3*v[i ]+5/6*v[i+1]- 1/6*v[i+2] vi+1/2(-),1=-1/6*v[i-1]+5/6*v[i ]+ 1/3*v[i+1] vi+1/2(-),2= 1/3*v[i-2]-7/6*v[i-1]+11/6*v[i ] """abscoef,sign=coef_toabsstring(coef)ifisFirstElementandsign=='+':sign=' 'signed_coef_str=f"{sign}{abscoef:>{width-1}}"returnsigned_coef_strdefget_sign_and_abs_str(coef):"""将系数拆分为符号字符('+', '-', ' ')和绝对值字符串。"""ifcoef>=0:return'+',str(coef)else:return'-',str(-coef)defcompute_column_widths(coef_matrix):"""计算每列系数显示所需的最大宽度(含符号位)"""rows,cols=coef_matrix.shapewidths=np.empty(cols,dtype=int)forjinrange(cols):max_width=0foriinrange(rows):_,abs_str=get_sign_and_abs_str(coef_matrix[i,j])width=len(abs_str)+1# +1 for sign or spacemax_width=max(max_width,width)widths[j]=max_widthreturnwidthsdefbuild_variable_index_string(offset):"""将偏移量转为 '[i+2]', '[i-1]', '[i ]' 等字符串"""ifoffset==0:return"[i ]"elifoffset>0:returnf"[i+{offset}]"else:returnf"[i{offset}]"# offset already includes minus, e.g., -2 → [i-2]defbuild_variable_indices(start_offset,num_cols):"""生成每列对应的变量索引字符串列表"""return[build_variable_index_string(start_offset+j)forjinrange(num_cols)]defbuild_lhs_label(x_frac:Fraction,shift:int=0)->str:# 1. 计算总偏移 = shift + x_fractotal_offset=shift+x_frac# 2. 格式化总偏移字符串(带符号)iftotal_offset>=0:offset_str=f"+{total_offset}"# e.g., +1/2, +3/2else:offset_str=f"{total_offset}"# e.g., -1/2(Fraction 会自动带负号)# 3. 确定方向标志:由原始 x_frac 的符号决定(非 total_offset!)direction='-'ifx_frac>=0else'+'# 4. 拼接returnf"vi{offset_str}({direction})"defformat_stencil_row(row,jstart,cols,widths):terms=[]ioffset_strs=build_variable_indices(jstart,cols)forjinrange(cols):term_str=format_signed_coef(row[j],j==0,widths[j])terms.append(f"{term_str}*v{ioffset_strs[j]}")rhs_label=''.join(terms)returnrhs_labeldefprint_stencil_formula(coef_matrix,xfrac,ishift=0,base_row=0):rows,cols=coef_matrix.shapewidths=compute_column_widths(coef_matrix)lhs_label=build_lhs_label(xfrac,ishift)foriinrange(rows):r=base_rowifrows==1elseirow=coef_matrix[i]jstart=ishift-rrhs_label=format_stencil_row(row,jstart,cols,widths)print(f'{lhs_label},{r}={rhs_label}')print()defbuild_substencil_offset_map(sub_stencils):"""为每个空间偏移 k,记录 (模板索引 r, 系数)"""rows,cols=sub_stencils.shapeoffset_map=defaultdict(list)forrinrange(rows):forjinrange(cols):k=j-r# spatial offset: v[i + k]coef=sub_stencils[r,j]offset_map[k].append((r,coef))returnoffset_mapdefbuild_target_offset_map(target_row):""" target_row: 1D array like [1/30, -13/60, 47/60, 9/20, -1/20] assumes it corresponds to offsets [-2, -1, 0, 1, 2] """n=len(target_row)base_offset=-(n//2)offsets=list(range(base_offset,base_offset+n))# [-2,-1,0,1,2]return{k:target_row[i]fori,kinenumerate(offsets)}defbuild_linear_system(sub_stencils,target_offset_map):""" Build A x = b for WENO weights. Returns: A: np.ndarray of shape (num_equations, num_templates) b: np.ndarray of shape (num_equations,) offsets: list of spatial offsets (for labeling) """sub_offset_map=build_substencil_offset_map(sub_stencils)num_templates=sub_stencils.shape[0]# Get all spatial offsets that appear in targetoffsets=sorted(target_offset_map.keys())A=[]b=[]forkinoffsets:row=[Fraction(0)for_inrange(num_templates)]forr,coefinsub_offset_map.get(k,[]):row[r]=coefA.append(row)b.append(target_offset_map[k])# Convert to float for numpy (or keep as Fraction for exact solve)A_float=np.array([[float(x)forxinrow]forrowinA])b_float=np.array([float(x)forxinb])returnA_float,b_float,offsetsdefsolve_weno_weights(sub_stencils,target_offset_map):A,b,offsets=build_linear_system(sub_stencils,target_offset_map)# Solve Ax = b in least-squares sensex,residuals,rank,s=np.linalg.lstsq(A,b,rcond=None)print("Solved WENO weights:")fori,wiinenumerate(x):print(f"d[{i}] = {wi:.6f} ≈ {Fraction(wi).limit_denominator(100)}")# Verify residualiflen(residuals)>0:print(f"Residual norm: {np.sqrt(residuals[0]):.2e}")else:# Exact solution (rank-deficient or square)residual=np.linalg.norm(A@x-b)print(f"Residual norm: {residual:.2e}")returnxdefprint_weno_equations(sub_stencils,target_dict):offset_map=build_substencil_offset_map(sub_stencils)all_offsets=sorted(target_dict.keys())rows,cols=sub_stencils.shapeweights=", ".join(f"d[{i}]"foriinrange(rows))print(f"WENO linear system (for weights {weights}):\n")forkinall_offsets:terms=[]forr,coefinoffset_map.get(k,[]):terms.append(f"d[{r}] * ({coef})")lhs=" + ".join(terms)iftermselse"0"rhs=target_dict[k]print(f"v[i{k:+}] : {lhs} = {rhs}")print()defcompute_weno_linear_weights(row_matrix,mymat):sub_stencils=mymat# Build target maptarget_dict=build_target_offset_map(row_matrix)print_weno_equations(sub_stencils,target_dict)# Solveweights=solve_weno_weights(mymat,target_dict)defcompute_weno_linear_weights_new(order):xfrac=Fraction(1,2)k=orderkh=2*k-1mymatL=generate_left_stencils(k)row_matL=compute_optimal_reconstruction_stencil(kh,xfrac)compute_weno_linear_weights(row_matL,mymatL)mymatR=generate_right_stencils(k)row_matR=compute_optimal_reconstruction_stencil(kh,-xfrac)compute_weno_linear_weights(row_matR,mymatR)defsolve_weno_linear_weights(optimal_stencil:np.ndarray,sub_stencils:np.ndarray)->np.ndarray:""" Solve for linear weights d such that: optimal_stencil ≈ sum_j d[j] * sub_stencils[j] Prints the linear system and solved weights. """# Build target maptarget_dict=build_target_offset_map(optimal_stencil)print_weno_equations(sub_stencils,target_dict)# Solveweights=solve_weno_weights(sub_stencils,target_dict)returnweightsdefdemo_weno_linear_weights(weno_r:int):""" Demonstrate linear weight computation for WENO-r scheme. Parameters: weno_r (int): Number of substencils (e.g., 3 for WENO5, 2 for WENO3) """x_half=Fraction(1,2)global_stencil_width=2*weno_r-1# e.g., 5 for WENO3# Left-biased (v_{i+1/2}^-)substencils_L=generate_weno_substencils(stencil_width=weno_r,x_point=x_half)optimal_L=compute_optimal_reconstruction_stencil(stencil_width=global_stencil_width,x_point=x_half)weights_L=solve_weno_linear_weights(optimal_L,substencils_L)# Right-biased (v_{i-1/2}^+)substencils_R=generate_weno_substencils(stencil_width=weno_r,x_point=-x_half)optimal_R=compute_optimal_reconstruction_stencil(stencil_width=global_stencil_width,x_point=-x_half)weights_R=solve_weno_linear_weights(optimal_R,substencils_R)returnweights_L,weights_Rif__name__=="__main__":maxk=3forkinrange(1,maxk+1):print(f"\n=== WENO{2*k-1} ===")demo_weno_linear_weights(weno_r=k)
目标:在点 x = xᵢ + 0.5Δx 处用五个点 v_{i-2}∼v_{i+2} 构造五阶精度插值。
令 Δx = 1(不失一般性),求系数 a, b, c, d, e 使得
\[ a v_{i-2} + b v_{i-1} + c v_i + d v_{i+1} + e v_{i+2} = v(x_i + 0.5) + O(\Delta x^5) \]
将 v_{i+k} 在 xᵢ 处泰勒展开并代入,要求 0∼4 阶导数系数精确匹配:
\[
\begin{cases}
a + b + c + d + e = 1 \\[6pt]
-2a - b + 0c + d + 2e = 0.5 \\[6pt]
4a + b + 0c + d + 4e = 0.25 \\[6pt]
-8a - b + 0c + d + 8e = 0.125 \\[6pt]
16a + b + 0c + d + 16e = 0.0625
\end{cases}
\]
解得:
\[ a = \frac{2}{60},\; b = -\frac{13}{60},\; c = \frac{47}{60},\; d = \frac{27}{60},\; e = -\frac{3}{60} \]