private void Draw3DGraph ( ) {...
Criado em: 5 de junho de 2025
Criado em: 5 de junho de 2025
textprivate void Draw3DGraph ( ) { double h = (b - a)/n; double k = (d - c)/m; double f ( double x , double y ) { return Math . PI * Math . PI * ( x * x + y * y ) * Math . Sin ( Math . PI * x * y ); } double gran_left ( int j ) { return Math . Sin ( Math . PI * ( c + ( ( j / ( n - 1 ) ) + 1 ) * k ) ); } double gran_right ( int j ) { return 0; } double gran_dawn ( int j ) { return Math . Sin ( Math . PI * 2 * ( a + ( j % ( n - 1 ) + 1 ) * h ) ); } double gran_top ( int j ) { return Math . Sin ( Math . PI * 3 * ( a + ( j % ( n - 1 ) + 1 ) * h ) ); } double eps = Math.Pow(10, -9); int Nmax = 10000; int S = 0; double eps_max = 0; double eps_curr = 0; double[] v = new double[(n-1)*(m-1)]; double[] r = new double[(n-1)*(m-1)]; double[] Ar = new double[(n-1)*(m-1)]; double scalar_top = 0; double scalar_dawn = 0; double tau = 0; double v_old = 0; bool condition = false; while ( !condition ) { scalar_top = 0; scalar_dawn = 0; tau = 0; for ( int j = 0 ; j < ( n - 1 ) * ( m - 1 ) ; j++ ) { r [ j ] = 2 * v [ j ] / ( h * h + k * k ); if ( j + 1 < ( n - 1 ) * ( m - 1 ) ) { r [ j ] -= v [ j + 1 ] / ( h * h ); } if ( j - 1 >= 0 ) { r [ j ] -= v [ j - 1 ] / ( h * h ); } if ( j + n - 1 < ( n - 1 ) * ( m - 1 ) ) { r [ j ] -= v [ j + n - 1 ] / ( k * k ); } if ( j - n + 1 >= 0 ) { r [ j ] -= v [ j - n + 1 ] / ( k * k ); } r [ j ] -= f ( a + ( j % ( n - 1 ) + 1 ) * h , c + ( ( j / ( n - 1 ) ) + 1 ) * k ); if ( j % ( n - 1 ) == 0 ) { r [ j ] -= gran_left ( j ) / ( h * h ); } if ( ( j + 1 ) % ( n - 1 ) == 0 ) { r [ j ] -= gran_right ( j ) / ( h * h ); } if ( j < ( n - 1 ) ) { r [ j ] -= gran_dawn ( j ) / ( k * k ); } if ( ( n - 1 ) * ( m - 1 ) - j <= n - 1 ) { r [ j ] -= gran_top ( j ) / ( k * k ); } } for ( int j = 0 ; j < ( n - 1 ) * ( m - 1 ) ; j++ ) { Ar [ j ] = 2 * r [ j ] / ( h * h + k * k ); if ( j + 1 < ( n - 1 ) * ( m - 1 ) ) { Ar [ j ] -= r [ j + 1 ] / ( h * h ); } if ( j - 1 >= 0 ) { Ar [ j ] -= r [ j - 1 ] / ( h * h ); } if ( j + n - 1 < ( n - 1 ) * ( m - 1 ) ) { Ar [ j ] -= r [ j + n - 1 ] / ( k * k ); } if ( j - n + 1 >= 0 ) { Ar [ j ] -= r [ j - n + 1 ] / ( k * k ); } scalar_top += Ar [ j ] * r [ j ]; scalar_dawn += Ar [ j ] * Ar [ j ]; } tau = scalar_top / scalar_dawn; eps_max = 0; for ( int j = 0 ; j < ( n - 1 ) * ( m - 1 ) ; j++ ) { v_old = v [ j ]; v [ j ] -= tau * r [ j ]; eps_curr = Math . Abs ( v_old - v [ j ] ); if ( eps_curr > eps_max ) { eps_max = eps_curr; } } S++; if ( ( eps_max < eps ) || ( S >= Nmax ) ) { condition = true; } } // Создаём MeshBuilder с указанием generateNormals = true var meshBuilder = new MeshBuilder(true, false); // Создаём массив точек Point3D[,] points = new Point3D[n - 1, m - 1]; for ( int i = 0 ; i < n - 1 ; i++ ) { for ( int j = 0 ; j < m - 1 ; j++ ) { double x = a + (i + 1) * h; double y = c + (j + 1) * k; double z = v [ i + j * (n - 1)]; points [ i , j ] = new Point3D ( x , y , z ); } } // Добавляем квады в правильном порядке for ( int i = 0 ; i < n - 2 ; i++ ) // По строкам (Y) { for ( int j = 0 ; j < m - 2 ; j++ ) // По столбцам (X) { // Прямой порядок (внешняя сторона) meshBuilder . AddQuad ( points [ i , j ] , points [ i + 1 , j ] , points [ i + 1 , j + 1 ] , points [ i , j + 1 ] ); // Обратный порядок (внутренняя сторона) meshBuilder . AddQuad ( points [ i , j + 1 ] , points [ i + 1 , j + 1 ] , points [ i + 1 , j ] , points [ i , j ] ); } } // Создаём 3D-модель var mesh = new GeometryModel3D { Geometry = meshBuilder.ToMesh(), Material = MaterialHelper.CreateMaterial(Brushes.Red) }; // Добавляем модель в сцену var viewport = FindName("HelixViewport3D") as HelixViewport3D; if ( viewport != null ) { viewport . Children . Add ( new ModelVisual3D { Content = mesh } ); // viewport . Children . Add ( new GridLinesVisual3D ( ) ); viewport . Children . Add ( new CoordinateSystemVisual3D ( ) ); } } - это мой метод для решения задачи Дирихле для уравнения Пуассона с помощью метода минимальных невязок - подозреваю что само численное решение находится правильно - но похоже отрисовка не верна я вижу очень "колючую" поверхность с очень резкими переходами, но может быть ошибка и с самим методом, проанализируй код, найди проблему
Алгоритм минимальных невязок сам по себе
v
, r
, Ar
формируются только по внутренним узлам ((n-1) × (m-1)
), формула правой части f(x,y)
и граничные условия выглядят корректно.eps_max
; если она достигается, решение в массиве v
обычно уже гладкое (при умеренных n, m
и невысоком eps
).→ Вероятность, что «колючки» приходят именно из численной схемы, невелика.
Главная ошибка — индексация соседей
В обоих циклах, где вычисляются r[j]
и Ar[j]
, горизонтальные соседи проверяются только так
csharpif (j + 1 < size) { … } // правый if (j - 1 >= 0) { … } // левый
но не проверяется переход через границу строки.
j % (n-1) == n-2
(последний внутренний узел строки) j+1
уже относится к началу следующей строки, а не к правому соседу.j % (n-1) == 0
, индекс j-1
уходит на предыдущую строку.Из-за этого каждая строка «сшивается» с соседней, что даёт резкие скачки потенциала и «иголки» на поверхности.
Как исправить
csharpbool hasRight = ((j + 1) < size) && ((j + 1) % (n - 1) != 0); bool hasLeft = (j % (n - 1) != 0); if (hasRight) { r[j] -= v[j + 1] / (h * h); } if (hasLeft) { r[j] -= v[j - 1] / (h * h); } … if (hasRight) { Ar[j] -= r[j + 1] / (h * h); } if (hasLeft) { Ar[j] -= r[j - 1] / (h * h); }
Аналогично добавить условия для Ar
.
Визуализация: дублирование граней
Вы выводите каждый квад двумя вызовами AddQuad
: прямым и обратным.
Helix Toolkit уже может отрисовывать заднюю сторону материала (через BackMaterial
), а удвоенные полигоны лежат в одной плоскости → Z-fighting и «шум».
Оставьте только один вызов (или удалите второй):
csharpmeshBuilder.AddQuad(points[i,j], points[i+1,j], points[i+1,j+1], points[i,j+1]);
Если нужна двусторонняя модель, создайте материал так:
csharpvar red = MaterialHelper.CreateMaterial(Brushes.Red); var mesh = new GeometryModel3D { Geometry = meshBuilder.ToMesh(), Material = red, BackMaterial = red // задняя сторона };
Мелкие улучшения качества сетки
Point3D[,] points
индексы согласованы: z = v[i + j*(n-1)]
. После починки соседей поверхность станет гладкой.u(x,y) = sin(πx) sin(πy)
|v – u|
— если она мала, система считается правильно.j±1
(и для Ar
то же самое).BackMaterial
вместо дублирования.n, m
или уменьшить eps
для более тонкой сетки.После этих изменений характерная «ершистость» пропадёт, и график станет гладким, как ожидается для решения уравнения Пуассона.
textprivate void Draw3DGraph ( ) { double h = (b - a)/n; double k = (d - c)/m; double f ( double x , double y ) { return Math . PI * Math . PI * ( x * x + y * y ) * Math . Sin ( Math . PI * x * y ); } double gran_left ( int j ) { return Math . Sin ( Math . PI * ( c + ( ( j / ( n - 1 ) ) + 1 ) * k ) ); } double gran_right ( int j ) { return 0; } double gran_dawn ( int j ) { return Math . Sin ( Math . PI * 2 * ( a + ( j % ( n - 1 ) + 1 ) * h ) ); } double gran_top ( int j ) { return Math . Sin ( Math . PI * 3 * ( a + ( j % ( n - 1 ) + 1 ) * h ) ); } double eps = Math.Pow(10, -12); int Nmax = 25000; int S = 0; double eps_max = 0; double eps_curr = 0; double[] v = new double[(n-1)*(m-1)]; double[] r = new double[(n-1)*(m-1)]; double[] Ar = new double[(n-1)*(m-1)]; double scalar_top = 0; double scalar_dawn = 0; double tau = 0; double v_old = 0; bool condition = false; while ( !condition ) { scalar_top = 0; scalar_dawn = 0; tau = 0; for ( int j = 0 ; j < ( n - 1 ) * ( m - 1 ) ; j++ ) { r [ j ] = 2 * v [ j ] / ( h * h + k * k ); bool hasRight = ((j + 1) % (n - 1) != 0); bool hasLeft = (j % (n - 1) != 0); if ( hasRight ) { r [ j ] -= v [ j + 1 ] / ( h * h ); } else { r [ j ] -= gran_right ( j ) / ( h * h ); } if ( hasLeft ) { r [ j ] -= v [ j - 1 ] / ( h * h ); } else { r [ j ] -= gran_left ( j ) / ( h * h ); } if ( j + n - 1 < ( n - 1 ) * ( m - 1 ) ) { r [ j ] -= v [ j + n - 1 ] / ( k * k ); } else { r [ j ] -= gran_top ( j ) / ( k * k ); } if ( j - n + 1 >= 0 ) { r [ j ] -= v [ j - n + 1 ] / ( k * k ); } else { r [ j ] -= gran_dawn ( j ) / ( k * k ); } r [ j ] -= f ( a + ( j % ( n - 1 ) + 1 ) * h , c + ( ( j / ( n - 1 ) ) + 1 ) * k ); } for ( int j = 0 ; j < ( n - 1 ) * ( m - 1 ) ; j++ ) { Ar [ j ] = 2 * r [ j ] / ( h * h + k * k ); bool hasRight = ((j + 1) % (n - 1) != 0); bool hasLeft = (j % (n - 1) != 0); if ( hasRight ) { Ar [ j ] -= r [ j + 1 ] / ( h * h ); } if ( hasLeft ) { Ar [ j ] -= r [ j - 1 ] / ( h * h ); } if ( j + n - 1 < ( n - 1 ) * ( m - 1 ) ) { Ar [ j ] -= r [ j + n - 1 ] / ( k * k ); } if ( j - n + 1 >= 0 ) { Ar [ j ] -= r [ j - n + 1 ] / ( k * k ); } scalar_top += Ar [ j ] * r [ j ]; scalar_dawn += Ar [ j ] * Ar [ j ]; } tau = scalar_top / scalar_dawn; eps_max = 0; for ( int j = 0 ; j < ( n - 1 ) * ( m - 1 ) ; j++ ) { v_old = v [ j ]; v [ j ] -= tau * r [ j ]; eps_curr = Math . Abs ( v_old - v [ j ] ); if ( eps_curr > eps_max ) { eps_max = eps_curr; } } S++; if ( ( eps_max < eps ) || ( S >= Nmax ) ) { condition = true; } } // Создаём MeshBuilder с указанием generateNormals = true var meshBuilder = new MeshBuilder(true, false); // Создаём массив точек Point3D[,] points = new Point3D[n - 1, m - 1]; for ( int i = 0 ; i < n - 1 ; i++ ) { for ( int j = 0 ; j < m - 1 ; j++ ) { double x = a + (i + 1) * h; double y = c + (j + 1) * k; double z = v [ i + j * (n - 1)]; points [ i , j ] = new Point3D ( x , y , z ); } } // Добавляем квады в правильном порядке for ( int i = 0 ; i < n - 2 ; i++ ) // По строкам (Y) { for ( int j = 0 ; j < m - 2 ; j++ ) // По столбцам (X) { // Прямой порядок (внешняя сторона) meshBuilder . AddQuad ( points [ i , j ] , points [ i + 1 , j ] , points [ i + 1 , j + 1 ] , points [ i , j + 1 ] ); // Обратный порядок (внутренняя сторона) meshBuilder . AddQuad ( points [ i , j + 1 ] , points [ i + 1 , j + 1 ] , points [ i + 1 , j ] , points [ i , j ] ); } } - у меня всё ещё есть подозрения что что-то не то - сейчас я вижу странный плоский график, видно только граничные условия но в остальном только горизонтальная плоскость - скорее всего проблема всё ещё в реализации метода минимальных невязок но может быть и в способе отрисовки
pythondef solve(n,m,a,b,c,d,eps=1e-12,Nmax=25000): h=(b-a)/n k=(d-c)/m size=(n-1)*(m-1) v=[0.0]*size def f(x,y): return math.pi*math.pi*(x*x+y*y)*math.sin(math.pi*x*y) def gran_left(j): return math.sin(math.pi*(c+((j//(n-1))+1)*k)) def gran_right(j): return 0.0 def gran_dawn(j): return math.sin(math.pi*2*(a+((j%(n-1))+1)*h)) def gran_top(j): return math.sin(math.pi*3*(a+((j%(n-1))+1)*h)) S=0 while True: r=[0.0]*size for j in range(size): rj=2*v[j]/(h*h+k*k) hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: rj-=v[j+1]/(h*h) else: rj-=gran_right(j)/(h*h) if hasLeft: rj-=v[j-1]/(h*h) else: rj-=gran_left(j)/(h*h) if j+n-1 < size: rj-=v[j+n-1]/(k*k) else: rj-=gran_top(j)/(k*k) if j - (n-1) >=0: rj-=v[j-(n-1)]/(k*k) else: rj-=gran_dawn(j)/(k*k) rj-=f(a+((j%(n-1))+1)*h, c+((j//(n-1))+1)*k) r[j]=rj Ar=[0.0]*size scalar_top=0 scalar_dawn=0 for j in range(size): Arj=2*r[j]/(h*h+k*k) hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: Arj-=r[j+1]/(h*h) if hasLeft: Arj-=r[j-1]/(h*h) if j+n-1 < size: Arj-=r[j+n-1]/(k*k) if j-(n-1)>=0: Arj-=r[j-(n-1)]/(k*k) Ar[j]=Arj scalar_top+=Arj*r[j] scalar_dawn+=Arj*Arj tau=scalar_top/scalar_dawn eps_max=0 for j in range(size): v_old=v[j] v[j]-=tau*r[j] eps_curr=abs(v_old-v[j]) if eps_curr>eps_max: eps_max=eps_curr S+=1 if eps_max<eps or S>=Nmax: break return v,S n=10 m=10 a=0 b=1 c=0 d=1 v,S=solve(n,m,a,b,c,d) min(v), max(v), S (-105.26043846648234, 104.83635916889433, 25000)
pythonv,_=solve(4,4,0,1,0,1,eps=1e-8,Nmax=10000) v [0.07336390908563158, -0.11728603451688821, 0.9247662470744537, -1.5314994782106461, -1.1891844637465945, 1.8278624532554086, -1.4894471082011156, -1.5863509349168945, 1.6292406716232273]
pythondef first_iter(n,m,a,b,c,d): h=(b-a)/n k=(d-c)/m size=(n-1)*(m-1) v=[0.0]*size # compute r def f(x,y): return math.pi*math.pi*(x*x+y*y)*math.sin(math.pi*x*y) def gran_left(j): return math.sin(math.pi*(c+((j//(n-1))+1)*k)) def gran_right(j): return 0.0 def gran_dawn(j): return math.sin(math.pi*2*(a+((j%(n-1))+1)*h)) def gran_top(j): return math.sin(math.pi*3*(a+((j%(n-1))+1)*h)) r=[0.0]*size for j in range(size): rj=2*v[j]/(h*h+k*k) hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: rj-=v[j+1]/(h*h) else: rj-=gran_right(j)/(h*h) if hasLeft: rj-=v[j-1]/(h*h) else: rj-=gran_left(j)/(h*h) if j+n-1 < size: rj-=v[j+n-1]/(k*k) else: rj-=gran_top(j)/(k*k) if j-(n-1)>=0: rj-=v[j-(n-1)]/(k*k) else: rj-=gran_dawn(j)/(k*k) rj-=f(a+((j%(n-1))+1)*h, c+((j//(n-1))+1)*k) r[j]=rj # Ar Ar=[0.0]*size scalar_top=0 scalar_dawn=0 for j in range(size): Arj=2*r[j]/(h*h+k*k) hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: Arj-=r[j+1]/(h*h) if hasLeft: Arj-=r[j-1]/(h*h) if j+n-1 < size: Arj-=r[j+n-1]/(k*k) if j-(n-1)>=0: Arj-=r[j-(n-1)]/(k*k) Ar[j]=Arj scalar_top+=Arj*r[j] scalar_dawn+=Arj*Arj tau=scalar_top/scalar_dawn eps_max=0 for j in range(size): v_old=v[j] v[j]-=tau*r[j] eps_curr=abs(v_old-v[j]) eps_max=max(eps_max,eps_curr) return tau, eps_max, v[:10] tau, eps_max, sample=first_iter(20,20,0,1,0,1) tau, eps_max, sample (-0.0012198152747109094, 0.49304200553745514, [-0.227106392701711, -0.28679813537187576, -0.3947476058514145, -0.46406137795229024, -0.4879568323046862, -0.46409776478335646, -0.39482320869476845, -0.2869186120076701, -0.1509517683656024, -0.00023850555226476858])
pythondef solve_bug(n,m,a,b,c,d,eps=1e-8,Nmax=10000): h=(b-a)/n k=(d-c)/m size=(n-1)*(m-1) v=[0.0]*size def f(x,y): return math.pi*math.pi*(x*x+y*y)*math.sin(math.pi*x*y) def gran_left(j): return math.sin(math.pi*(c+((j//(n-1))+1)*k)) def gran_right(j): return 0.0 def gran_dawn(j): return math.sin(math.pi*2*(a+((j%(n-1))+1)*h)) def gran_top(j): return math.sin(math.pi*3*(a+((j%(n-1))+1)*h)) S=0 while True: r=[0.0]*size for j in range(size): hcoef=2/(h*h+k*k) rj=hcoef*v[j] hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: rj-=v[j+1]/(h*h) else: rj-=gran_right(j)/(h*h) if hasLeft: rj-=v[j-1]/(h*h) else: rj-=gran_left(j)/(h*h) if j+n-1 < size: rj-=v[j+n-1]/(k*k) else: rj-=gran_top(j)/(k*k) if j - n + 1 >= 0: rj-=v[j - n + 1]/(k*k) # bug, should be j-(n-1) else: rj-=gran_dawn(j)/(k*k) rj-=f(a+((j%(n-1))+1)*h, c+((j//(n-1))+1)*k) r[j]=rj # Ar Ar=[0.0]*size scalar_top=0 scalar_dawn=0 for j in range(size): Arj=2*r[j]/(h*h+k*k) hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: Arj-=r[j+1]/(h*h) if hasLeft: Arj-=r[j-1]/(h*h) if j+n-1 < size: Arj-=r[j+n-1]/(k*k) if j - n +1 >= 0: Arj-=r[j - n +1]/(k*k) Ar[j]=Arj scalar_top+=Arj*r[j] scalar_dawn+=Arj*Arj tau=scalar_top/scalar_dawn eps_max=0 for j in range(size): v_old=v[j] v[j]-=tau*r[j] eps_curr=abs(v_old-v[j]) if eps_curr>eps_max: eps_max=eps_curr S+=1 if eps_max<eps or S>=Nmax: break return v,S v_bug,S_bug=solve_bug(10,10,0,1,0,1,eps=1e-8,Nmax=10000) min(v_bug), max(v_bug), S_bug (-5.649774576538222, 5.2257067728514315, 10000)
pythonv_bug,_=solve_bug(20,20,0,1,0,1,eps=1e-12,Nmax=50000) min(v_bug), max(v_bug) (-3.950313340025232, 4.315375001668348)
pythondef solve_correct(n,m,a,b,c,d,eps=1e-8,Nmax=10000): h=(b-a)/n k=(d-c)/m size=(n-1)*(m-1) v=[0.0]*size def f(x,y): return math.pi*math.pi*(x*x+y*y)*math.sin(math.pi*x*y) def gran_left(j): return math.sin(math.pi*(c+((j//(n-1))+1)*k)) def gran_right(j): return 0.0 def gran_dawn(j): return math.sin(math.pi*2*(a+((j%(n-1))+1)*h)) def gran_top(j): return math.sin(math.pi*3*(a+((j%(n-1))+1)*h)) coef_center=2*(1/(h*h)+1/(k*k)) iter_count=0 while True: r=[0.0]*size for j in range(size): rj=coef_center*v[j] hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: rj-=v[j+1]/(h*h) else: rj-=gran_right(j)/(h*h) if hasLeft: rj-=v[j-1]/(h*h) else: rj-=gran_left(j)/(h*h) if j+(n-1)<size: rj-=v[j+(n-1)]/(k*k) else: rj-=gran_top(j)/(k*k) if j-(n-1)>=0: rj-=v[j-(n-1)]/(k*k) else: rj-=gran_dawn(j)/(k*k) rj-=f(a+((j%(n-1))+1)*h, c+((j//(n-1))+1)*k) r[j]=rj Ar=[0.0]*size scalar_top=0 scalar_dawn=0 for j in range(size): Arj=coef_center*r[j] hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: Arj-=r[j+1]/(h*h) if hasLeft: Arj-=r[j-1]/(h*h) if j+(n-1)<size: Arj-=r[j+(n-1)]/(k*k) if j-(n-1)>=0: Arj-=r[j-(n-1)]/(k*k) Ar[j]=Arj scalar_top+=Arj*r[j] scalar_dawn+=Arj*Arj tau=scalar_top/scalar_dawn eps_max=0 for j in range(size): v_old=v[j] v[j]-=tau*r[j] eps_curr=abs(v_old-v[j]) if eps_curr>eps_max: eps_max=eps_curr iter_count+=1 if eps_max<eps or iter_count>=Nmax: break return v,iter_count v_corr,_=solve_correct(20,20,0,1,0,1,eps=1e-8,Nmax=10000) min(v_corr), max(v_corr) (-0.6834369932146113, 0.9059329751538501)
pythondef first_tau_bug(n,m): a=0;b=1;c=0;d=1 h=(b-a)/n;k=(d-c)/m size=(n-1)*(m-1) v=[0]*size def f(x,y): return math.pi*math.pi*(x*x+y*y)*math.sin(math.pi*x*y) def gran_left(j):return math.sin(math.pi*(c+((j//(n-1))+1)*k)) def gran_right(j):return 0. def gran_dawn(j):return math.sin(math.pi*2*(a+((j%(n-1))+1)*h)) def gran_top(j):return math.sin(math.pi*3*(a+((j%(n-1))+1)*h)) r=[0]*size for j in range(size): res=2*v[j]/(h*h+k*k) hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: res-=v[j+1]/(h*h) else: res-=gran_right(j)/(h*h) if hasLeft: res-=v[j-1]/(h*h) else: res-=gran_left(j)/(h*h) if j+n-1 < size: res-=v[j+n-1]/(k*k) else: res-=gran_top(j)/(k*k) if j - n +1 >=0: res-=v[j-n+1]/(k*k) # bug else: res-=gran_dawn(j)/(k*k) res-=f(a+((j%(n-1))+1)*h,c+((j//(n-1))+1)*k) r[j]=res Ar=[0]*size scalar_top=0; scalar_dawn=0 for j in range(size): res=2*r[j]/(h*h+k*k) hasRight=((j+1)%(n-1)!=0) hasLeft=(j%(n-1)!=0) if hasRight: res-=r[j+1]/(h*h) if hasLeft: res-=r[j-1]/(h*h) if j+n-1 < size: res-=r[j+n-1]/(k*k) if j-n+1 >=0: res-=r[j-n+1]/(k*k) Ar[j]=res scalar_top+=res*r[j] scalar_dawn+=res*res return scalar_top,scalar_dawn scalar_top, scalar_dawn=first_tau_bug(50,50) scalar_top, scalar_dawn (-1153244653127.512, 5774389904308677.0)
下面这几个细节如果不改,内部 vᵢⱼ 始终几乎为 0 → 你看到的就是一块平板。
它们都集中在 算子 A 的系数和相邻节点的索引 上——一旦写错,迭代会算出“伪残差”,τ
会极小,更新量 < ε,结果等于没更新。
csharp// 竖向“下”邻居(上一行)——漏掉了括号 if (j - (n - 1) >= 0) // ✓ 正确 { r[j] -= v[j - (n - 1)] / (k * k); ... }
同样的索引在 Ar 循环里也要改;
否则你其实拿的是对角线上下移一格的点,而不是正上方的点。
2 / (h² + k²)
→ 2 * (1/h² + 1/k²)
离散拉普拉斯
移到左边就是
csharpdouble a0 = 2 * (1.0/(h*h) + 1.0/(k*k)); // 中心 // r[j] = a0 * v[j] - v[j±1]/h² - v[j±(n-1)]/k² - f
Ar 里同理。
用错误的小系数会让 A r
和 r
基本正交,τ≈0
,你看到的只有边界值铺在一张近乎零的内区平面上。
AddQuad
或给 BackMaterial
多画一次四边形=同一平面两张面片重叠,深度缓冲抖动导致要么闪烁要么被裁掉,看着就像没数据。保留正面即可:
csharpmeshBuilder.AddQuad( points[i, j], points[i+1, j], points[i+1, j+1], points[i, j+1]); // 双面效果需要时: var mat = MaterialHelper.CreateMaterial(Brushes.Red); mesh.BackMaterial = mat;
csharpConsole.WriteLine($"{S}\t{eps_max:e}\t{tau:e}");
eps_max
应该单调下降;tau
若很快掉到 1e-12 量级就是算子有问题;eps
放宽到 1e-8,确认曲面有起伏,再逐步收紧。另外,可将解析解
塞进 gran_*
/ f
做对照,|v-u|
一眼就能看出误差在哪里。
改完 ①②③ 再跑一次,你会得到一张平滑、起伏和右端项相符的 3D 曲面,而不是“仅有边框的平板”。