Эффективная структура данных или метод для управления данными построения, которые растут со временем - PullRequest
7 голосов
/ 05 сентября 2011

Я хотел бы спросить, является ли следующий способ управления результатом построения графиков эффективным использованием Mathematica и есть ли более «функциональный» способ сделать это. (может использовать Sow, Reap и тому подобное.)

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

Чтобы показать график, необходимо сохранить точки данных во время его работы.

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

Manipulate[
 sol = First@NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, y[0] == Pi/4, y'[0] == 0}, 
                      y, {t, time, time + 1}];

 With[{angle = y /. sol},
  (
   ListPlot[{{time, angle[time]}}, AxesLabel -> {"time", "angle"}, 
            PlotRange -> {{0, max}, {-Pi, Pi}}]
   )
  ],

 {{time, 0, "run"}, 0, max, Dynamic@delT, ControlType -> Trigger},
 {{delT, 0.1, "delT"}, 0.1, 1, 0.1, Appearance -> "Labeled"},

 TrackedSymbols :> {time},
 Initialization :> (max = 10)
 ]

enter image description here

Вышесказанное не интересно, так как видна только движущаяся точка, а не полный путь решения.

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

Проблема в том, что временной шаг может измениться, и чем он меньше, тем больше данных будет сгенерировано.

Но так как я знаю наименьший возможный временной шаг (который в данном примере составляет 0,1 секунды), и я знаю общее время выполнения (которое здесь составляет 10 секунд), то я знаю, сколько выделить.

Мне также нужен индекс для отслеживания буфера. Используя этот метод, вот способ сделать это:

Manipulate[

 If[time == 0, index = 0];

 sol = First@NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, y[0] == Pi/4,y'[0] == 0}, 
                      y, {t, time, time + 1}];

 With[{angle = y /. sol},
  (
   index += 1;
   buffer[[index]] = {time, angle[time]};

   ListPlot[buffer[[1 ;; index]], Joined -> True, AxesLabel -> {"time", "angle"}, 
            PlotRange -> {{0, 10}, {-Pi, Pi}}]
   )
  ],

 {{time, 0, "run"}, 0, 10, Dynamic@delT, AnimationRate -> 1, ControlType -> Trigger},
 {{delT, 0.1, "delT"}, 0.1, 1, 0.1, Appearance -> "Labeled"},

 {{buffer, Table[{0, 0}, {(max + 1)*10}]}, None},
 {{index, 0}, None},

 TrackedSymbols :> {time},
 Initialization :> (max = 10)
 ]

enter image description here

Для справки: когда я делаю что-то подобное в Matlab, у него есть хорошая возможность для построения графиков, называемая «держись». Чтобы можно было построить точку, а затем сказать «держись», это означает, что следующий график не сотрет то, что уже есть на графике, а добавит его.

Я не нашел чего-то подобного в Mathematica, то есть обновил текущий сюжет на лету.

Я также не хотел использовать Append [] и AppendTo [] для построения буфера во время его работы, поскольку это будет медленным и неэффективным.

Мой вопрос: есть ли более эффективный способ Mathematica (который может быть быстрее и более изящным) для выполнения типичной задачи, такой как описанная выше, помимо того, что я делаю?

спасибо,

UPDATE:

На вопрос, почему не решить ODE все сразу. Да, это возможно, но это сильно упрощает задачу по частям, в том числе по соображениям производительности. Вот пример с одой с начальными условиями:

Manipulate[
 If[time == 0, index = 0];
 sol = First@
   NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, y[0] == y0, 
     y'[0] == yder0}, y, {t, time, time + 1}];

 With[{angle = (y /. sol)[time]},
  (
   index += 1;
   buffer[[index]] = {time, angle};

   ListPlot[buffer[[1 ;; index]], Joined -> True, 
    AxesLabel -> {"time", "angle"}, 
    PlotRange -> {{0, 10}, {-Pi, Pi}}])],

 {{time, 0, "run"}, 0, 10, Dynamic@delT, AnimationRate -> 1, 
  ControlType -> Trigger}, {{delT, 0.1, "delT"}, 0.1, 1, 0.1, 
  Appearance -> "Labeled"},
 {{y0, Pi/4, "y(0)"}, -Pi, Pi, Pi/100, Appearance -> "Labeled"},
 {{yder0, 0, "y'(0)"}, -1, 1, .1, Appearance -> "Labeled"},

 {{buffer, Table[{0, 0}, {(max + 1)*10}]}, None},
 {{index, 0}, None},

 TrackedSymbols :> {time},
 Initialization :> (max = 10)
 ]

enter image description here

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

Кроме того, я заметил, что я могу получить намного лучшую скорость, решая систему для меньших временных отрезков с течением времени, чем все это сразу. Затраты на вызов NDSolve очень малы. Но когда длительность времени для NDsolve для велика, могут возникнуть проблемы, когда кто-то запросит у NDSolve более высокую точность, как в опциях AccuracyGoal ->, PrecisionGoal ->, чего я не смог бы, когда временной интервал очень большой.

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

ОБНОВЛЕНИЕ 2

Я сравнил следующие 4 метода для 2 тестовых случаев:

метод путаницы [j] [j] (Велизарий) AppendTo (предложено Sjoerd) Динамический связанный список (Леонид) (с и без SetAttributes[linkedList, HoldAllComplete]) предварительно выделить буфер (Nasser)

Я так и сделал, выполнив 2 случая: один на 10 000 баллов, а второй на 20 000 баллов.Я оставил там команду Plot [[], но не отображаю ее на экране, это исключает любые накладные расходы на фактический рендеринг.

Я использовал Timing [] вокруг цикла Do, который перебирает базовую логику, которая называется NDSolve, и перебирает временной интервал с использованием приращений delT, как описано выше.Манипуляции не использовались.

Я использовал Quit [] перед каждым запуском.

Для метода Леонида я изменил столбец [], который у него был в цикле Do.В конце я проверил, но вычерчивал данные, используя метод getData [], что результат в порядке.

Весь код, который я использовал, приведен ниже.Я сделал таблицу, которая показывает результаты для 10 000 баллов и 20 000.Время в секундах:

 result = Grid[{
   {Text[Style["method", Bold]], 
    Text[Style["number of elements", Bold]], SpanFromLeft},
   {"", 10000, 20000},
   {"", SpanFromLeft},
   {"buffer", 129, 571},
   {"AppendTo", 128, 574},
   {"tangle[j][j]", 612, 2459},
   {"linkedList with SetAttribute", 25, 81},
   {"linkedList w/o SetAttribute", 27, 90}}
  ]

enter image description here

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

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

метод буфера

delT = 0.01; max = 100; index = 0;
buffer = Table[{0, 0}, {(max + 1)*1/delT}];

Timing[
 Do[
  sol = First@
    NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, y[0] == Pi/4, 
      y'[0] == 0}, y, {t, time, time + 1}];

  With[{angle = y /. sol},
   (index += 1;
    buffer[[index]] = {time, angle[time]};
    foo = 
     ListPlot[buffer[[1 ;; index]], Joined -> True, 
      AxesLabel -> {"time", "angle"}, 
      PlotRange -> {{0, 10}, {-Pi, Pi}}]
    )
   ], {time, 0, max, delT}
  ]
 ]

метод AppendTo

Clear[y, t];
delT = 0.01; max = 200;
buffer = {{0, 0}};  (*just a hack to get ball rolling, would not do this in real code*)

Timing[
 Do[
  sol = First@
    NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, y[0] == Pi/4, 
      y'[0] == 0}, y, {t, time, time + 1}];

  With[{angle = y /. sol},
   (AppendTo[buffer, {time, angle[time]}];
    foo = 
     ListPlot[buffer, Joined -> True, AxesLabel -> {"time", "angle"}, 
      PlotRange -> {{0, 10}, {-Pi, Pi}}]
    )
   ], {time, 0, max, delT}
  ]
 ]

метод путаницы [j] [j]

Clear[y, t];
delT = 0.01; max = 200;
Timing[
 Do[
  sol = First@
    NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, y[0] == Pi/4, 
      y'[0] == 0}, y, {t, time, time + 1}];
  tangle[time] = y /. sol;
  foo = ListPlot[
    Table[{j, tangle[j][j]}, {j, .1, max, delT}],
    AxesLabel -> {"time", "angle"},
    PlotRange -> {{0, max}, {-Pi, Pi}}
    ]
  , {time, 0, max, delT}
  ]
 ]

метод динамического связанного списка

Timing[
 max = 200;

 ClearAll[linkedList, toLinkedList, fromLinkedList, addToList, pop, 
  emptyList];
 SetAttributes[linkedList, HoldAllComplete];
 toLinkedList[data_List] := Fold[linkedList, linkedList[], data];
 fromLinkedList[ll_linkedList] := 
  List @@ Flatten[ll, Infinity, linkedList];
 addToList[ll_, value_] := linkedList[ll, value];
 pop[ll_] := Last@ll;
 emptyList[] := linkedList[];

 Clear[getData];

 Module[{ll = emptyList[], time = 0, restart, plot, y},

  getData[] := fromLinkedList[ll];

  plot[] := Graphics[
    {
     Hue[0.67`, 0.6`, 0.6`],
     Line[fromLinkedList[ll]]
     },
    AspectRatio -> 1/GoldenRatio,
    Axes -> True,
    AxesLabel -> {"time", "angle"},
    PlotRange -> {{0, 10}, {-Pi, Pi}},
    PlotRangeClipping -> True
    ];

  DynamicModule[{sol, angle, llaux, delT = 0.01},

   restart[] := (time = 0; llaux = emptyList[]);
   llaux = ll;

   sol := 
    First@NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, y[0] == Pi/4, 
       y'[0] == 0}, y, {t, time, time + 1}];
   angle := y /. sol;

   ll := With[{res = 
       If[llaux === emptyList[] || pop[llaux][[1]] != time,
        addToList[llaux, {time, angle[time]}],
        (*else*)llaux]
      },
     llaux = res
     ];

   Do[
    time += delT;
    plot[]
    , {i, 0, max, delT}
    ]
   ]
  ]
 ]

спасибо завсем помогите.

Ответы [ 3 ]

3 голосов
/ 05 сентября 2011

Я не знаю, как получить то, что вы хотите, с помощью Manipulate, но мне, кажется, удалось получить что-то близкое с пользовательским Dynamic.Следующий код будет: использовать связанные списки, чтобы быть достаточно эффективными, остановить / возобновить ваш график с помощью кнопки, и получить собранные до настоящего времени данные по запросу в любой момент времени:

ClearAll[linkedList, toLinkedList, fromLinkedList, addToList, pop, emptyList];
SetAttributes[linkedList, HoldAllComplete]; 
toLinkedList[data_List] := Fold[linkedList, linkedList[], data];
fromLinkedList[ll_linkedList] := List @@ Flatten[ll, Infinity, linkedList];
addToList[ll_, value_] := linkedList[ll, value];
pop[ll_] := Last@ll;
emptyList[] := linkedList[];


Clear[getData];
Module[{ll = emptyList[], time = 0, restart, plot, y},
   getData[] := fromLinkedList[ll];
   plot[] := 
      Graphics[{Hue[0.67`, 0.6`, 0.6`], Line[fromLinkedList[ll]]}, 
        AspectRatio -> 1/GoldenRatio, Axes -> True, 
        AxesLabel -> {"time", "angle"}, PlotRange -> {{0, 10}, {-Pi, Pi}}, 
        PlotRangeClipping -> True];
   DynamicModule[{sol, angle, llaux, delT = 0.1},
     restart[] := (time = 0; llaux = emptyList[]);
     llaux = ll;
     sol := First@
        NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, y[0] == Pi/4, y'[0] == 0}, 
             y, {t, time, time + 1}];
     angle := y /. sol;
     ll := With[{res =
              If[llaux === emptyList[] || pop[llaux][[1]] != time, 
                 addToList[llaux, {time, angle[time]}],
                 (* else  *)
                 llaux]},
              llaux = res];
     Column[{
        Row[{Dynamic@delT, Slider[Dynamic[delT], {0.1, 1., 0.1}]}],
        Dynamic[time, {None, Automatic, None}],
        Row[{
          Trigger[Dynamic[time], {0, 10, Dynamic@delT}, 
               AppearanceElements -> { "PlayPauseButton"}], 
          Button[Style["Restart", Small], restart[]]
        }],
        Dynamic[plot[]]
      }, Frame -> True]
  ]
]

Связанные списки здесь заменяютваш buffer и вам не нужно предварительно выделять и заранее знать, сколько точек данных у вас будет.plot[] - это пользовательская низкоуровневая функция построения графиков, хотя мы, вероятно, с таким же успехом могли бы использовать ListPlot.Вы используете кнопку «Воспроизведение» для остановки и возобновления печати, а также пользовательскую кнопку «Перезапуск» для сброса параметров.

Вы можете в любое время позвонить getData[], чтобы получить список данных.накоплено до сих пор, вот так:

In[218]:= getData[]
Out[218]= {{0,0.785398},{0.2,0.771383},{0.3,0.754062},{0.4,0.730105},{0.5,0.699755},
{0.6,0.663304},{0.7,0.621093},{0.8,0.573517},{0.9,0.521021},{1.,0.464099},
{1.1,0.403294},{1.2,0.339193},{1.3,0.272424}}
1 голос
/ 05 сентября 2011

Мне просто интересно, почему вы хотите решить DE по частям. Это может быть решено для всего интервала сразу. Также нет необходимости помещать NDSolve в Манипулятор. Это не нужно решать снова и снова, когда запускается тело Manipulate. Plot само по себе достаточно быстро для построения растущего графика на каждом временном шаге. Следующий код делает то, что вы хотите, без необходимости какого-либо хранилища.

sol = First@
   NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]]==0,y[0] == Pi/4,y'[0] == 0}, y, {t, 0, 10}];
eps = 0.000001;
Manipulate[
 With[{angle = y /. sol}, 
   Plot[angle[t], {t, 0, time + eps}, 
    AxesLabel -> {"time", "angle"}, 
    PlotRange -> {{0, max}, {-Pi, Pi}}
   ]
 ], 
 {{time, 0, "run"}, 0, max,Dynamic@delT, ControlType -> Trigger}, 
 {{delT, 0.1, "delT"}, 0.1, 1, 0.1, Appearance -> "Labeled"}, TrackedSymbols :> {time}, 
 Initialization :> (max = 10)
]

enter image description here

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

1 голос
/ 05 сентября 2011

Не память эффективна вообще, но ее преимущество в том, что она требует лишь небольшой модификации вашего первого кода:

Clear[tangle]; 
Manipulate[
 sol = First@NDSolve[{y''[t] + 0.1 y'[t] + Sin[y[t]] == 0, 
                    y[0]  == Pi/4, 
                    y'[0] == 0}, 
             y, {t, time, time + 1}];

 (tangle[time] = y /. sol; 
  ListPlot[Table[{j, tangle[j][j]}, {j, .1, max, delT}], 
    AxesLabel -> {"time", "angle"}, 
    PlotRange -> {{0, max}, {-Pi, Pi}}]), 
 {{time, 0, "run"}, 0, max, Dynamic@delT, ControlType -> Trigger}, 
 {{delT, 0.1, "delT"}, 0.1, 1, 0.1, Appearance -> "Labeled"}, 
 TrackedSymbols :> {time}, 
 Initialization :> {(max = 10); i = 0}]

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...