SaveDefinitions считается опасным - PullRequest
19 голосов
/ 05 июля 2011

SaveDefinitions хороший вариант Manipulate.Это заставляет Manipulate хранить любые определения, использованные для его создания, на панели «Манипуляции».Созданный таким образом Манипулятор может быть скопирован в пустой блокнот и все равно будет работать самостоятельно.Кроме того, ваша рабочая тетрадь, содержащая множество таких манипуляций, также не превращается в розовую шкатулку с напечатанными сообщениями об ошибках под ней при открытии.Отлично!

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

В этом сценарии вы хотите создатьManipulate, показывающий график хорошей волнистой функции, поэтому вы определяете это (пожалуйста, сделайте размер окна, подобный этому, это важно):

enter image description here

Определение хорошеепоэтому мы сохраняем его в следующий раз и делаем его ячейкой инициализации.Затем мы добавляем Manipulate и выполняем его тоже.

f[x_] := x^2

Manipulate[
 Plot[n f[x], {x, -3, 3}],
 {n, 1, 4},
 SaveDefinitions -> True
 ]

Все прекрасно работает, Манипулирование действительно сияет, это хороший день.

enter image description here

Просто будучи вашим параноиком, вы проверяете, правильно ли это определениеОК:

enter image description here

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

enter image description here

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

На следующий день.Вы начинаете свою работу снова.Вы оцениваете ячейки инициализации в своей записной книжке.Определение все еще хорошо?Проверьте.

enter image description here

Теперь прокрутите вниз до окна «Манипуляции» (нет необходимости повторного выполнения благодаря SaveDefinitions), немного поиграйте с ползунком.И прокрутите назад.

enter image description here

Будучи параноиком, вы еще раз проверите определение f:

enter image description here

Loи вот, кто-то изменил определение за вашей спиной!И ничего не выполняется между вашей первой и второй проверкой Information (?) В соответствии с числами In [] (In[1]: def of f, In[2] first?, In[3] second?).

Что случилось?Ну, это конечно же Manipulate.A FullForm раскрывает свою внутреннюю структуру:

Manipulate[Plot[n*f[x],{x, -3, 3}],{{n, 2.44}, 1, 4},Initialization:>{f[x_] := x^2}]

Там у вас есть преступник.Часть инициализации блока снова определяет f, но это старая версия, потому что мы не пересмотрели Manipulate после изменения его определения.Как только окно манипуляции появляется на экране, оно оценивается, и вы возвращаете свое старое определение.Глобально!

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

Очевидно, я испытываю желание сказать, что это нежелательноповедение.Теперь по обязательному вопросу: что мы можем сделать, чтобы не допустить возникновения этого поведения за спиной Manipulate, кроме повторного выполнения каждого Manipulate в вашей записной книжке каждый раз, когда вы меняете определение, которое может бытьиспользуется ими?

Ответы [ 2 ]

10 голосов
/ 05 июля 2011

Вот попытка. Идея состоит в том, чтобы идентифицировать символы с DownValues или некоторыми другими ...Values внутри вашего манипулируемого кода и автоматически переименовывать их, используя вместо них уникальные переменные / символы. Идея здесь может быть выполнена довольно элегантно с помощью функции клонирования символов, что я считаю полезным время от времени. Функция clone ниже клонирует данный символ, производя символ с такими же глобальными определениями:

Clear[GlobalProperties];
GlobalProperties[] :=
  {OwnValues, DownValues, SubValues, UpValues, NValues, FormatValues, 
      Options, DefaultValues, Attributes};


Clear[unique];
unique[sym_] :=
 ToExpression[
    ToString[Unique[sym]] <> 
       StringReplace[StringJoin[ToString /@ Date[]], "." :> ""]];


Attributes[clone] = {HoldAll};
clone[s_Symbol, new_Symbol: Null] :=
  With[{clone = If[new === Null, unique[Unevaluated[s]], ClearAll[new]; new],
        sopts = Options[Unevaluated[s]]},
     With[{setProp = (#[clone] = (#[s] /. HoldPattern[s] :> clone)) &},
        Map[setProp, DeleteCases[GlobalProperties[], Options]];
        If[sopts =!= {}, Options[clone] = (sopts /. HoldPattern[s] :> clone)];
        HoldPattern[s] :> clone]]

Существует несколько вариантов реализации самой функции. Одним из них является введение функции с другим именем, принимающей те же аргументы, что и Manipulate, скажем myManipulate. Я буду использовать другую: мягкую перегрузку Manipulate через UpValues некоторой пользовательской оболочки, которую я представлю. Я назову это CloneSymbols. Вот код:

ClearAll[CloneSymbols];
CloneSymbols /: 
Manipulate[args___,CloneSymbols[sd:(SaveDefinitions->True)],after:OptionsPattern[]]:=
   Unevaluated[Manipulate[args, sd, after]] /.
     Cases[
       Hold[args],
       s_Symbol /; Flatten[{DownValues[s], SubValues[s], UpValues[s]}] =!= {} :> 
          clone[s],
       Infinity, Heads -> True];

Вот пример использования:

f[x_] := Sin[x];
g[x_] := x^2;

Обратите внимание, что для использования новой функциональности необходимо обернуть параметр SaveDefinitions->True в оболочку CloneSymbols:

Manipulate[Plot[ f[n g[x]], {x, -3, 3}], {n, 1, 4}, 
          CloneSymbols[SaveDefinitions -> True]]

Manipulate

Это не повлияет на определения исходных символов в коде внутри Manipulate, поскольку это были их клоны, определения которых были сохранены и теперь используются при инициализации. Мы можем посмотреть на FullForm для этого Manipulate, чтобы подтвердить, что:

Manipulate[Plot[f$37782011751740542578125[Times[n,g$37792011751740542587890[x]]],
   List[x,-3,3]],List[List[n,1.9849999999999999`],1,4],RuleDelayed[Initialization,
     List[SetDelayed[f$37782011751740542578125[Pattern[x,Blank[]]],Sin[x]],
       SetDelayed[g$37792011751740542587890[Pattern[x,Blank[]]],Power[x,2]]]]]

В частности, вы можете изменить определения функций на

f[x_]:=Cos[x];
g[x_]:=x;

Затем переместите ползунок Manipulate, полученный выше, и затем проверьте определения функций

?f
Global`f
f[x_]:=Cos[x]

?g
Global`g
g[x_]:=x

Это Manipulate не зависит ни от чего и может быть безопасно скопировано и скопировано. Здесь происходит следующее: сначала мы находим все символы с нетривиальными DownValues, SubValues или UpValues (возможно, можно также добавить OwnValues) и используем Cases и clone для создания их клоны на лету. Затем мы лексически заменяем все клонированные символы их клонами внутри Manipulate, а затем позволяем Manipulate сохранить определения для клонов. Таким образом, мы делаем «снимок» задействованных функций, но никак не влияем на исходные функции.

Уникальность клонов (символов) была решена с помощью функции unique. Однако обратите внимание, что, хотя Manipulate -ы, полученные таким способом, не угрожают исходным определениям функций, они, как правило, все еще зависят от них, поэтому нельзя считать их полностью независимыми от чего-либо. Нужно пройтись по дереву зависимостей и клонировать все символы там, а затем восстановить их взаимозависимости, чтобы создать полностью автономный «снимок» в Manipulate. Это выполнимо, но сложнее.

EDIT

По запросу @Sjoerd я добавляю код для случая, когда мы хотим, чтобы наши Manipulate обновляли изменения функции, но не хотим, чтобы они активно вмешивались и изменяли какие-либо глобальные определения. Я предлагаю вариант техники «указателя»: мы снова заменим имена функций новыми символами, но вместо того, чтобы клонировать эти новые символы после наших функций, мы будем использовать опцию Manipulate Initialization, чтобы просто сделать эти символы "указатели" на наши функции, например, Initialization:>{new1:=f,new2:=g}. Ясно, что повторная оценка такого кода инициализации не может повредить определениям f или g, и в то же время наши Manipulate -ы станут реагировать на изменения в этих определениях.

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

ClearAll[SavePointers];
SavePointers /: 
Manipulate[args___,SavePointers[sd :(SaveDefinitions->True)],
after:OptionsPattern[]] :=
Module[{init},
  With[{ptrrules = 
    Cases[Hold[args], 
      s_Symbol /; Flatten[{DownValues[s], SubValues[s], UpValues[s]}] =!= {} :> 
         With[{pointer = unique[Unevaluated[s]]},
            pointer := s;
            HoldPattern[s] :> pointer], 
            Infinity, Heads -> True]},
           Hold[ptrrules] /. 
              (Verbatim[HoldPattern][lhs_] :> rhs_ ) :> (rhs := lhs) /. 
               Hold[defs_] :> 
                 ReleaseHold[
                      Hold[Manipulate[args, Initialization :> init, after]] /. 
                            ptrrules /. init :> defs]]]

С теми же определениями, что и раньше:

ClearAll[f, g];
f[x_] := Sin[x];
g[x_] := x^2;

Вот FullForm произведенных Manipulate:

In[454]:= 
FullForm[Manipulate[Plot[f[n g[x]],{x,-3,3}],{n,1,4},
     SavePointers[SaveDefinitions->True]]]

Out[454]//FullForm=   
Manipulate[Plot[f$3653201175165770507872[Times[n,g$3654201175165770608016[x]]],
List[x,-3,3]],List[n,1,4],RuleDelayed[Initialization,
List[SetDelayed[f$3653201175165770507872,f],SetDelayed[g$3654201175165770608016,g]]]]

Вновь созданные символы служат «указателями» на наши функции.Manipulate, созданные с использованием этого подхода, будут реагировать на обновления наших функций и в то же время безвредны для определений основных функций.Платой является то, что они не являются автономными и не будут отображаться правильно, если основные функции не определены.Таким образом, можно использовать либо CloneSymbols оболочку, либо SavePointers, в зависимости от того, что нужно.

5 голосов
/ 05 июля 2011

Ответ заключается в использовании ячейки инициализации в качестве инициализации для Manipulate:

Manipulate[
 Plot[n f[x], {x, -3, 3}], {n, 1, 4}, 
 Initialization :> FrontEndTokenExecute["EvaluateInitialization"]]

Вы также можете использовать DynamicModule:

DynamicModule[{f},
 f[x_] := x^2;
 Manipulate[Plot[n f[x], {x, -3, 3}], {n, 1, 4}]]

Вы не делаетев этом случае нужно SaveDefinitions -> True.

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

В ответ на комментарий Шёрда.Используя следующую простую технику, вам не нужно копировать определение повсеместно и обновлять все копии, если вы измените определение (но вам все равно нужно пересмотреть свой код, чтобы получить обновленный Manipulate):

DynamicModule[{f}, f[x_] := x^2;
  list = Manipulate[Plot[n^# f[x], {x, -3, 3}], {n, 2, 4}] & /@ Range[3]];
list // Row
...