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

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
 ]

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

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

Вот и кто-то изменил определение за вашей спиной! И ничего не выполнялось между вашей первой и второй проверкой Information (?) В соответствии с номерами In [] (In[1]: def 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 в вашем ноутбуке каждый раз, когда вы меняете определение, которое может быть используемые ими?

Ответы

Ответ 1

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

ИЗМЕНИТЬ

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

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

Ответ 2

Ответ заключается в использовании ячейки инициализации в качестве инициализации для 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.

ИЗМЕНИТЬ

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

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