Как я могу ссылаться на конкретную точку моей функции внутри NDSolve? - PullRequest
4 голосов
/ 07 августа 2011

Проблема:

Я пытаюсь решить это дифференциальное уравнение:

K[x_, x1_] := 1;
NDSolve[{A''[x] == Integrate[K[x, x1] A[x1], {x1, 0, 1}], 
         A[0] == 0, A'[1] == 1}, A[x], x]

и я получаю ошибки (Function::slotn и NDSolve::ndnum)
(он должен возвращать числовую функцию, равную 3/16 x^2 + 5/8 x)

Я ищу способ решить это дифференциальное уравнение: есть ли способ записать его в лучшем виде, чтобы NDSolve его понял? Может ли помочь другая функция или пакет?

Примечание 1: В моей полной задаче K[x, x1] - это не 1 - это зависит (сложным образом) от x и x1.
Примечание 2: Наивное извлечение двух сторон уравнения относительно x не сработает, поскольку интегральные пределы являются определенными.

Мое первое впечатление:

Кажется, что Mathematica не любит, когда я ссылаюсь на точку в A[x] - те же ошибки возникают, когда я делаю эту упрощенную версию:

NDSolve[{A''[x] == A[0.5], A[0] == 0, A'[1] == 1}, A[x], x]

(должна возвращать числовую функцию, равную 2/11 x^2 + 7/11 x)

В этом случае можно избежать этой проблемы, аналитически решив A''[x] == c, а затем найдя c, но в моей первой задаче это, похоже, не работает - оно только преобразует дифференциальное уравнение в интегральное, которое ( N) DSolve не решает впоследствии.

Ответы [ 3 ]

7 голосов
/ 08 августа 2011

Я могу предложить способ свести ваше уравнение к интегральному уравнению, которое может быть решено численно путем приближения его ядра к матрице, тем самым сводя интеграцию к умножению матриц.

Во-первых, ясно, что уравнение может быть интегрировано дважды по x, сначала от 1 до x, а затем от 0 до x, так что:

enter image description here

Теперь мы можем дискретизировать это уравнение, поместив его на эквидистантную сетку:

enter image description here

Здесь A[x] становится вектороми интегрированное ядро ​​iniIntK становится матрицей, а интеграция заменяется умножением матрицы.Затем задача сводится к системе линейных уравнений.

Самый простой случай (который я рассмотрю здесь) - это когда ядро ​​iniIntK может быть получено аналитически - в этом случае этот метод будет довольно быстрым.Вот функция для создания интегрированного ядра в виде чистой функции:

Clear[computeDoubleIntK]
computeDoubleIntK[kernelF_] :=
  Block[{x, x1},
   Function[
    Evaluate[
      Integrate[
         Integrate[kernelF[y, x1], {y, 1, x}] /. x -> y, {y, 0, x}] /. 
         {x -> #1, x1 -> #2}]]];

В нашем случае:

In[99]:= K[x_,x1_]:=1;

In[100]:= kernel = computeDoubleIntK[K]
Out[100]= -#1+#1^2/2&

Вот функция для получения матрицы ядра и rh, svector:

 computeDiscreteKernelMatrixAndRHS[intkernel_, a0_, aprime1_ , 
    delta_, interval : {_, _}] :=
  Module[{grid, rhs, matrix},
    grid = Range[Sequence @@ interval, delta];
    rhs = a0 + aprime1*grid; (* constant plus a linear term *)
    matrix = 
      IdentityMatrix[Length[grid]] - delta*Outer[intkernel, grid, grid];
    {matrix, rhs}]

Чтобы дать очень грубое представление о том, как это может выглядеть (я использую здесь delta = 1/2):

In[101]:= computeDiscreteKernelMatrixAndRHS[kernel,0,1,1/2,{0,1}]

Out[101]= {{{1,0,0},{3/16,19/16,3/16},{1/4,1/4,5/4}},{0,1/2,1}}

Теперь нам нужно решить линейное уравнение, иинтерполировать результат, что делается с помощью следующей функции:

Clear[computeSolution];
computeSolution[intkernel_, a0_, aprime1_ , delta_, interval : {_, _}] :=
With[{grid = Range[Sequence @@ interval, delta]},
  Interpolation@Transpose[{
    grid,
    LinearSolve @@ 
      computeDiscreteKernelMatrixAndRHS[intkernel, a0, aprime1, delta,interval]
  }]]

Здесь я назову его с delta = 0.1:

In[90]:= solA = computeSolution[kernel,0,1,0.1,{0,1}]
Out[90]= InterpolatingFunction[{{0.,1.}},<>] 

Теперь мы построим график зависимости результата от точногоаналитическое решение, найденное @Sasha, а также ошибка:

enter image description here

Я специально выбрал delta достаточно большим, чтобы ошибки были видны.Если вы выбрали delta скажем 0.01, графики будут визуально идентичны.Конечно, цена взятия меньшего delta - это необходимость производить и решать большие матрицы.

Для ядер, которые могут быть получены аналитически, основное узкое место будет в LinearSolve, но на практике это довольно быстро (для матриц не слишком больших).Когда ядра не могут быть интегрированы аналитически, основным узким местом будет вычисление ядра во многих точках (создание матрицы. Обратная матрица имеет большую асимптотическую сложность, но это начнет играть роль для действительно больших матриц - которые не нужны вэтот подход, так как он может быть объединен с итеративным - см. ниже).Обычно вы определяете:

intK[x_?NumericQ, x1_?NumericQ] := NIntegrate[K[y, x1], {y, 1, x}]
intIntK[x_?NumericQ, x1_?NumericQ] := NIntegrate[intK[z, x1], {z, 0, x}]

В качестве способа ускорения в таких случаях вы можете предварительно вычислить ядро ​​intK в сетке, а затем интерполировать, и то же самое для intIntK.Это, однако, приведет к дополнительным ошибкам, которые вам придется оценить (учесть).

Сама сетка не должна быть равноудаленной (я просто использовал ее для простоты), но может (и, вероятно, должна) быть адаптивнойи вообще неравномерно.

В качестве окончательной иллюстрации рассмотрим уравнение с нетривиальным, но символически интегрируемым ядром:

In[146]:= sinkern =  computeDoubleIntK[50*Sin[Pi/2*(#1-#2)]&]

Out[146]= (100 (2 Sin[1/2 \[Pi] (-#1+#2)]+Sin[(\[Pi] #2)/2] 
     (-2+\[Pi] #1)))/\[Pi]^2&

In[157]:= solSin = computeSolution[sinkern,0,1,0.01,{0,1}]
Out[157]= InterpolatingFunction[{{0.,1.}},<>]

enter image description here

Вот несколько проверок:

In[163]:= Chop[{solSin[0],solSin'[1]}]
Out[163]= {0,1.}

In[153]:= 
diff[x_?NumericQ]:=
  solSin''[x] - NIntegrate[50*Sin[Pi/2*(#1-#2)]&[x,x1]*solSin[x1],{x1,0,1}];

In[162]:= diff/@Range[0,1,0.1]

Out[162]=  {-0.0675775,-0.0654974,-0.0632056,-0.0593575,-0.0540479,-0.0474074,
   -0.0395995,-0.0308166,-0.0212749,-0.0112093,0.000369261}

В заключение хочу подчеркнуть, что для этого метода необходимо провести тщательный анализ ошибок, чего я не делал.

РЕДАКТИРОВАТЬ

Вы также можете использовать этот метод, чтобы получить начальное приближенное решение, а затем итеративно улучшить его, используя FixedPoint или другими способами - таким образом, вы будете иметь относительно быструю сходимость и сможете достичь требуемой точности безнеобходимость строить и решать огромные матрицы.

4 голосов
/ 08 августа 2011

Это дополняет подход Леонида Шифрина. Мы начнем с линейной функции, которая интерполирует значение и первую производную в начальной точке. Мы используем это при интеграции с данной функцией ядра. Затем мы можем выполнить итерацию, используя каждое предыдущее приближение в интегрированном ядре, которое используется для создания следующего приближения.

Я показываю пример ниже, используя более сложное ядро, чем просто постоянную функцию. Я проведу его через две итерации и покажу таблицы расхождений.

kernel[x_, y_] := Sqrt[x]/(y^2 + 1/5)*Sin[x^2 + y]
intkern[x_?NumericQ, aa_] := 
 NIntegrate[kernel[x, y]*aa[y], {y, 0, 1}, MinRecursion -> 2, 
  AccuracyGoal -> 3]

Clear[a];
a0 = 0;
a1 = 1;
a[0][x_] := a0 + a1*x

soln1 = a[1][x] /. 
   First[NDSolve[{(a[1]^\[Prime]\[Prime])[x] == intkern[x, a[0], y], 
      a[1][0] == a0, a[1][1] == a1}, a[1][x], {x, 0, 1}]];
a[1][x_] = soln1;

In[283]:= Table[a[1]''[x] - intkern[x, a[1]], {x, 0., 1, .1}]

Out[283]= {4.336808689942018*10^-19, 0.01145100326794241, \
0.01721655945379122, 0.02313249302884235, 0.02990900241909161, \
0.03778448183557359, 0.04676409320217928, 0.05657128568058478, \
0.06665818935524814, 0.07624149919589895, 0.08412643746245929}

In[285]:= 
soln2 = a[2][x] /. 
   First[NDSolve[{(a[2]^\[Prime]\[Prime])[x] == intkern[x, a[1]], 
      a[2][0] == a0, a[2][1] == a1}, a[2][x], {x, 0, 1}]];
a[2][x_] = soln2;

In[287]:= Table[a[2]''[x] - intkern[x, a[2]], {x, 0., 1, .1}]

Out[287]= {-2.168404344971009*10^-19, -0.001009606971360516, \
-0.00152476679745811, -0.002045817184941901, -0.002645356229312557, \
-0.003343218015068372, -0.004121109614310836, -0.004977453722712966, \
-0.005846840469889258, -0.006731367269472544, -0.007404971586975062}

Таким образом, у нас есть ошибки менее 0,01 на данном этапе. Не так уж плохо. Один недостаток состоит в том, что было довольно медленно получить второе приближение. Могут быть способы настроить NDSolve, чтобы улучшить это.

Это дополняет метод Леонида по двум причинам.

(1) Если это не очень хорошо сходилось, потому что начальное линейное приближение не было достаточно близко к истинному результату, вместо этого можно было бы начать с приближения, найденного по схеме конечных разностей. Это было бы сродни тому, что он сделал.

(2) Он сам в значительной степени указал на это, как на метод, который мог бы следовать его и производить уточнения.

Даниэль Лихтблау

0 голосов
/ 07 августа 2011

То, как ваше уравнение записывается в настоящее время A''[x] == const, и чем константа не зависит от x.Следовательно, решение всегда имеет вид квадратичного полинома.Ваша проблема затем сводится к решению для неопределенных коэффициентов:

In[13]:= A[x_] := a2 x^2 + a1 x + a0;

In[14]:= K[x_, x1_] := 1;

In[16]:= Solve[{A''[x] == Integrate[K[x, x1] A[x1], {x1, 0, 1}], 
  A[0] == 0, A'[1] == 1}, {a2, a1, a0}]

Out[16]= {{a2 -> 3/16, a1 -> 5/8, a0 -> 0}}

In[17]:= A[x] /. First[%]

Out[17]= (5 x)/8 + (3 x^2)/16
...