SaveDefinitions считается опасным

19

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

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

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

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

f[x_] := x^2

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

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

Будучи самим собой параноиком, вы проверяете правильность определения:

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

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

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

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

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

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

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

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

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

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

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

    
задан Sjoerd C. de Vries 05.07.2011 в 10:13
источник
  • Я не вижу здесь настоящей проблемы. Вы показали конфликт двух методов инициализации. Если вы используете параметр SaveDefinitions -> True в Manipulate, вам просто не следует использовать ячейку инициализации для той же задачи! Он похож на создание двух ячеек инициализации в одном ноутбуке с разными определениями для одного и того же символа ... –  Alexey Popkov 05.07.2011 в 10:55
  • @alexey Я не согласен. Обе инициализации служат другой цели. Ясно, что SaveDefinitions не предназначено для глобального введения определений. И то, что он фактически спасает, скрыто. Неприятно для чего-то с глобальным эффектом. –  Sjoerd C. de Vries 05.07.2011 в 11:03
  • В этом смысле я согласен с вами. Это всего лишь плохая конструкция Manipulate. –  Alexey Popkov 05.07.2011 в 11:29
  • @Sjoerd, хотя то, что вы описываете, имеет полный смысл (и я согласен, что это очень плохо), я не могу воспроизвести его здесь, несмотря на следующие шаги! Любому другому удалось воспроизвести? –  Szabolcs 05.07.2011 в 12:05
  • Это случилось со мной много раз, тоже ... –  acl 05.07.2011 в 12:07
Показать остальные комментарии

2 ответа

10

Вот попытка. Идея состоит в том, чтобы идентифицировать символы с 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 , так как это были их клоны, определения которых были сохранены и теперь используются при инициализации. Мы можем посмотреть на 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 -s, полученные таким способом, не угрожают исходным определениям функций, они, как правило, все еще зависят от них, поэтому нельзя считать их полностью независимыми от чего-либо. Нужно пройтись по дереву зависимостей и клонировать все символы там, а затем восстановить их взаимозависимости, чтобы создать полностью автономный «снимок» в Manipulate. Это выполнимо, но более сложно.

ИЗМЕНИТЬ

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

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

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;

Вот Initialize от произведенного FullForm :

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 -s, построенный с помощью этого подхода, будет реагировать на обновления в наших функциях и в то же время безвреден для определений основных функций. Платой является то, что они не являются автономными и не будут отображаться правильно, если основные функции не определены. Таким образом, можно использовать либо оболочку Manipulate , либо CloneSymbols , в зависимости от того, что нужно.

    
ответ дан Leonid Shifrin 05.07.2011 в 11:08
  • Спасибо, Леонид. Мне нужно изучить это более внимательно, чем сейчас, но одно быстрое замечание о вашем замечании о том, что модуль может вызвать конфликты имен в новых сеансах: я понимаю, что использование DynamicModule должно помешать этому (см.: Reference.wolfram.com/mathematica/tutorial / ...) –  Sjoerd C. de Vries 05.07.2011 в 12:36
  • @Sjoerd Действительно, возможность использовать DynamicModule перешла мне в голову. Но переменные, созданные DynamicModule, принадлежат FrontEnd, и мне как-то лучше нравится использовать переменные ядра для определения функций. Возможно, я совершенно ошибаюсь в этом, я даже, кажется, помню некоторые обсуждения в MathGroup, где эта практика была обескуражена. –  Leonid Shifrin 05.07.2011 в 12:48
  • @Sjoerd Я упростил реализацию клона и внедрил уникальную уникальную функцию, которая должна решать проблему уникальности, а также для разных сеансов Mathematica. Теперь код должен быть легче переваривать. –  Leonid Shifrin 05.07.2011 в 13:43
  • Хорошо, я изучил это некоторое время. Это не самый простой способ понять, и я не уверен, понимаю ли я каждый его бит (например, новый параметр в клоне не совсем ясен), но похоже, что это предотвратит выполнение манипуляционной панели , Хорошая работа! OTOH, некоторые функции Manipulate, похоже, сейчас потеряны. Если вы измените функцию вне среды Manipulate, Manipulate не обновится, чтобы отразить это, как и раньше. Не уверен, хорошо это или плохо. –  Sjoerd C. de Vries 05.07.2011 в 23:30
  • @Sjoerd Новый параметр на всякий случай, если пользователь хочет поставить конкретный символ в качестве клона исходного символа (я изначально написал эту функцию для случая, когда эта опция была необходима). Затем он очищается и используется, иначе создается уникальный символ. Что касается потери полезной функциональности, у меня есть некоторые идеи по этому поводу, но они должны быть разработаны. Надеюсь скоро получить что-то лучшее. –  Leonid Shifrin 05.07.2011 в 23:50
Показать остальные комментарии
5

Ответ заключается в использовании ячейки инициализации в качестве инициализации для 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
    
ответ дан Alexey Popkov 05.07.2011 в 11:00
  • ОК. Я вижу, как первый может помочь. Однако для этого требуется, чтобы указанное определение находилось в ячейке инициализации. Я использовал это в своем примере, но это не обязательно для того, чтобы получить этот эффект сзади. Второй помогает, но только если нет других манипуляторов, которые используют одну и ту же функцию. Вам придется копировать определение везде. И обновите все копии, если вы измените определение. –  Sjoerd C. de Vries 05.07.2011 в 12:02
  • @Sjoerd См. обновленный ответ. –  Alexey Popkov 07.07.2011 в 18:20
  • Я в настоящее время путешествую. Я проверю через две недели. –  Sjoerd C. de Vries 08.07.2011 в 17:12