Лекция 3. Элементы управления и динамика

Элементы управления (Controls)

В Mathematica имеются многочисленные элементы управления (ползунки (Slider), флаговые кнопки (Checkbox), селективные кнопки (RadioButton) и т.д.). Эта особенность делает возможным организовывать пользователю удобный интерфейс. Приведем некоторые примеры.

Ползунок

Slider[]

Ползунок можно вставлять в формулы

1+Slider[]

Ползунок можно вставлять в формулы в виде ползунка, если скопировать его из вывода

1+

То же самое можно сделать, пометив ползунок и выполнив команду Ctrl+Shift+Enter, которая вычислит и заменит на результат ровно то выражение на входе, которое было помечено. Проверьте это на примере приведенного ниже выражения

1+Slider[]

Однако, как видно, ползунок не участвует в вычислениях. Если хочется использовать ползунок для задания конкретных значений, можно обрамить его DynamicSetting, а затем скопировать результат в поле ввода. Вычисление результата будет учитывать конкретное значение, заданное ползунком.

1+DynamicSetting[]

1+

Ползунок может иметь разные параметры, например
Slider[x] устанавливает ползунок в положение x (предполагается, что x лежит между 0 и 1)
Slider[x, {xmin, xmax}] устанавливает ползунок в положение x (предполагается, что x лежит между xmin и xmax)
Slider[x, {xmin, xmax, dx}] устанавливает ползунок в положение x (предполагается, что x лежит между xmin и xmax), причем ползунок двигается дискретно с шагом dx

Slider[0.5,{0,2,0.5}]

Аналогично работает двумерный ползунок Slider2D[]

DynamicSetting[Slider2D[]]

Еще несколько примеров

Manipulator[]
Checkbox[]
RadioButton[]
ColorSlider[]
Graphics[Locator[{0,0}],PlotRange->1]

Как правило, чтобы "оживить" элементы управления, их используют вместе с оператором Dynamic[], про который речь пойдет ниже.

Оператор Dynamic[]

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

x=3;
(x^2)

x=5
(x^2)

(заметим, что первый вывод по-прежнему дает значение 9, хотя "x" уже поменялся)

Если хочется, чтобы вывод результата вычисления выражения менялся при изменении входящих переменных, можно использовать Dynamic

Dynamic[(x^2)]

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

x=7;

x=∫y\[DifferentialD]y;

x=Plot[(x^2),{x,-1,1}];

Динамика и элементы управления

Ползунок не влияет на значение аргумента x

x=0.5;

Slider[x]

x

Если ползунок использовать вместе с Dynamic, то переменная x будет динамически обновлять свое значение при перемещении ползунка

{Slider[Dynamic[x]],Dynamic[x]}

x

Приведем пример использования динамики в двумерном ползунке.

{Slider2D[Dynamic[x]],Dynamic[x]}

{Slider[Dynamic[k]],Dynamic[Plot[Sin[k x],{x,0,20π},PlotRange->{{0,20π},{-1,1}}]]}

{Slider2D[Dynamic[r]],Dynamic[Plot3D[Cos[Norm[{x,y}-r]],{x,0,1},{y,0,1},Mesh->None]]}

Приведем примеры использования динамики в других элементах управления

Clear[x]
{Manipulator[Dynamic[x1]],Dynamic[x1]}
{Checkbox[Dynamic[x2]],Dynamic[x2]}
Map[RadioButton[Dynamic[x3],#]&,{1,2,3}]
{ColorSlider[Dynamic[col]],Dynamic[Graphics[{col,Disk[]}]]}
p={0.5,0.5};
Graphics[Locator[Dynamic[p]],PlotRange->1]
Dynamic[p]

Clear[k,x,x1,x2,p]

Локализация переменных

В приводимых ниже двух примерах ползунков оба они изменяются одинаково при изменении одного из них (так как "x" -  глобальная переменная).

{Slider[Dynamic[x]],Dynamic[x]}
{Slider[Dynamic[x]],Dynamic[x]}

Чтобы избавиться от такой зависимости, можно локализовать переменную "x" (по аналогии с тем, как это делалось в Module[])

DynamicModule[{x},{Slider[Dynamic[x]],Dynamic[x]}]
DynamicModule[{x},{Slider[Dynamic[x]],Dynamic[x]}]

Локализация с помощью DynamicModule имеет свои преимущества, в частности, при сохранении проекта и выхода из него текущие значения локализованных переменных сохраняются (этого не происходит со значениями глобальных переменных и локальных переменных из Module).

Второй аргумент в Dynamic[]

Когда Dynamic[] используется вместе с ползунком в виде Slider[Dynamic[expr]], при движении ползунка все время выполняется присвоение expr = new, где new - значение, вычисленное по положению ползунка. Если присвоение успешно, ползунок смещается, иначе - остается на месте.

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

DynamicModule[{x=0},{Slider[Dynamic[x]],Slider[Dynamic[1-x]]}]

Однако, если использовать второй аргумент в Dynamic, то с помощью него можно задать динамику первого аргумента

DynamicModule[{x=0},{Slider[Dynamic[x]],Slider[Dynamic[1-x,(x=1-#)&]]}]

Этим способом можно реализовать обратную функцию.

DynamicModule[{x=0},{Slider[Dynamic[x]],Slider[Dynamic[1-2x,(x=(1-#)/2)&],{-1,1}]}]

Таким образом, формат команды такой : Dynamic[expr, f], где f[expr, val]  вычисляется при каждом изменении значения val

В приводимом примере используем тот факт, что DateList[], выдающая временные данные в формате {год, месяц, день, час, минута, секунда}, так же, как и функция RandomReal[], самостоятельно не обновляется. Однако при обновлении второго аргумента динамики, первый также пересчитывается.

Slider[Dynamic[x]]
Dynamic[DateList[],Dynamic[x]]

Можно заставить самостоятельно обновляться Dynamic[], задав опцию  UpdateInterval
UpdateInterval -> t (обновляться не реже чем каждые t секунд)
UpdateInterval -> 0 (обновляться как можно чаще)
UpdateInterval -> Infinity (не обновляться)

Dynamic[DateList[],UpdateInterval->1]

Где нужно располагать Dynamic[]?

Оператор Dynamic[] ведет себя достаточно хитро, и далеко не каждое его расположение приводит к ожидаемому результату.  Опишем несколько важных принципов, которые позволяют избежать многочисленных ошибок.

1) Mathematica состоит из оболочки (front end), в которой мы сейчас работаем, и ядра (kernel), выполняющего вычисления выражений (перед тем, как выполнится первая команда, скажем, 2+2, набранная в оболочке и активированная командой оболочки shift+Enter, Mathematica загружает в память свое ядро и команда обрабатывается именно ядром; в дальнейшем ядро остается в памяти и производит вычисление выражений, активизированных той же командой shift+Enter). Изпользуя команду меню Evaluation/Quit Kernel/Local можно выгрузить ядро, завершив тем самым сеанс. Если затем выполнить команду shift+Enter, позиционировав предварительно курсор в какой-нибудь клетке, ядро вновь загрузится и команды из клетки будут обработаны ядром.

2) Главная особенность оператора Dynamic[] состоит в том, что он выполняется оболочкой, а не ядром. В частности, не возможно вычислить  функцию от переменной, обрамленной командой Dynamic[].

(хотя значение x подставляется в Sin[], тем не менее команда N[] не может вычислить значение Sin[10] )

x=10;
N[Sin[Dynamic[x]]]

x=.
Plot[Dynamic[(x^2)],{x,-1,1}]

3) Dynamic[expr] имеет атрибут HoldFirst, поэтому аргумент expr не меняется при вычислении

Attributes[Dynamic]
Dynamic[1+1]//InputForm

Dynamic[Evaluate[1+1]]//InputForm

Важное следствие: ошибки при создании списка динамических данных

(временная переменная i внутри data[[i]] не меняет своего значения, потому что Dynamic имеет атрибут HoldFirst)

data={.1,.5,.3,.9,.2};
Dynamic[data]
Table[Slider[Dynamic[data[[i]]]],{i,5}]

Чтобы конструкция заработала, можно использовать правило замены, чтобы итератор оказался снаружи Dynamic

data={.1,.5,.3,.9,.2};
Dynamic[data]
Table[Slider[Dynamic[data[[i]]]]/.i->ii,{ii,5}]

Еще один вариант - использовать команду With[{x=x0,y=y0,...},expr], состоящую в том, что при каждом появлении символов x, y ... они заменяются на x0, y0, ...

With[{y=p-1},1+y-y^2]

data={.1,.5,.3,.9,.2};
Dynamic[data]
Table[With[{i=i},Slider[Dynamic[data[[i]]]]],{i,5}]

4) "Dynamic" не выводится при стандартном выводе, хотя на самом деле присутствует, что можно увидеть, заменив формат вывод на InputForm.
Кроме того, при выводе в стандартном режиме аргумент expr из Dynamic[expr] визуализуется в вычисленном виде, не смотря на то, что реально Dynamic[expr] представлено невычисленным выражением expr.

Dynamic[1+1]
Dynamic[1+1]//InputForm

x=.

2+Slider[x]

Slider[x]

5) При использовании Dynamic[] с элементами управления нужно придерживаться следующего правила: 
первый аргумент элемента управления почти всегда обрамлен Dynamic, например,
Slider[Dynamic[x]], Checkbox[Dynamic[x]], RadioButton[Dynamic[x]] ...

{Dynamic[Slider[x]],Dynamic[x]}

6) Расположение Dynamic в правильном месте - высокое искусство. Часто это - самый внешний оператор, хотя, как мы уже видели на примере элементов контроля, это не всегда так. Кроме того, иногда имеется свобода в расположении Dynamic, а также Dynamic могут быть вложенными в друг друга.

Dynamic[Table[x,{i,10}]]
Table[Dynamic[x],{i,10}]

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

7) Dynamic может быть расположен в правой части опций, однако далеко не во всех случаях. Например, Dynamic не может регулировать PlotPoints, потому что Plot должен знать PlotPoints до того, как будет порождено то, что потом изобразится на экране (напомним, что Dynamic выполняется оболочкой, а не ядром). Приведем примеры возможного использования Dynamic в опциях.

(Style работает с оболочкой, настраивая вид вывода, поэтому Style может работать с Dynamic)

{Slider[Dynamic[h],{6,100}],Dynamic[Style["Это - текст",FontSize->h]]}
{Slider[Dynamic[h],{6,100}],Style["Это - текст",FontSize->Dynamic[h]]}

Dynamic может быть также использована с командой SetOptions.

SetOptions[Graphics,Background->Dynamic[Hue[x]]]
{Slider[Dynamic[x]],Graphics[Circle[]]}

Clear[h,x]

Функция Manipulate[]

Эта функция позволяет визуализировать зависимость выражения от параметров. Вот некоторые форматы этой команды.
Manipulate[expr,{u,umin,umax}] -параметр u меняется на отрезке от umin до umax (ползунок Slider);
Manipulate[expr,{u,{xmin,ymin},{xmax,ymax}}] - параметр u меняется в прямоугольнике, с нижним левым концом в {xmin,ymin} и верхним правым концом в {xmax,ymax} (двумерный ползунок Slider2D);
Manipulate[expr,{u,umin,umax,du}] параметр u меняется ползунком с шагом du ;
Manipulate[expr,{{u,uinit},umin,umax}]        параметру u присваивается начальное значение uinit;
Manipulate[expr,{{u,uinit},umin,umax,du}]  параметру u присваивается начальное значение uinit;
Manipulate[expr,{u,{u1,u2,...}}] параметр u пробегает дискретный набор значений u1, u2,...; 
           если список значений короткий, то u контролируется строкой кнопок (setter bar); 
           если список длинный - то выпадающим меню (Popup Menu); 
Manipulate[expr,{u,color}] параметр u задается с помощью цветового ползунка;
Manipulate[expr,{u,{True,False}}] параметр u задается с помощью флаговой кнопки (CheckBox);
Manipulate[expr,{u,Locator}] параметр u задается с помощью локатора (Locator);
Manipulate[expr,{{u,uinit},Locator}] начальное положение локатора равно uinit.
Можно также задавать не один параметр, а несколько.

Manipulate[Plot[λ ((f[a (x-loc[[1]])+b])^n)g[x-loc[[1]]]+loc[[2]],{x,0,pt[[1]]},PlotRange->{{0,pt[[1]]},{-pt[[2]],pt[[2]]}},PlotStyle->{color},Axes->axes],
{λ,1,2}, 
{pt,{2π,1},{4π,2}}, 
{n,1,5,1}, 
{{a,1},0,2},
{{b,1},0,2,0.2},
{f,{Sin,Cos,Tan,Cot}},
{g,{Sin,Cos,Tan,Cot,Sqrt,Log}},
{color,Red},
{axes,{True,False}},
{{loc,{0,0}},Locator}
]

Управление скоростью движения ползунка

Если при смешении ползунка удерживать клавишу Alt, изменение параметра замедлится в 20 раз. Если к Alt добавить Ctrl или Shift, движение замедлится еще в 20 раз (проделайте это на примере, приведенном выше).

Редактируемая визуализация текущего значения параметра, задаваемого ползунком, изменение этого значения, автоматизация процесса изменения, последовтельное изменение всех параметров (Autorun)

Если кликнуть на значок "+" справа от ползунка, то под ползунком возникнет окошко с текущим значением параметра, а также шесть кнопок справа от этого окошка. Окошко можно редактировать, изменяя тем самым значение параметра. Шесть кнопок позволяют задавать автоматическое изменение параметра (поэкспериментируйте с этими кнопками и разберитесь, за что они отвечают). Чтобы посмотреть зависимость от всех параметров, можно кликнуть на значок "+" в правом верхнем углу панели Manipulate и в появившемся меню выбрать Autorun. Появится управляющее окошко Autorun и параметры начнут последовательно менять свои значения. В управляющем окошке можно менять параметры демонстрации Autorun. Чтобы остановить процесс, нажмите в этом окошке на close.

Нередактируемая визуализация значения параметра, задаваемого ползунком

Чтобы вывести справа от ползунка текущее нередактируемое значение параметра, добавьте в список описания параметра опцию Appearance -> "Labeled"

Manipulate[Plot[Sin[a x+b],{x,0,2π},PlotRange->{{0,2π},{-1,1}}],
{{a,1},0,2},
{b,0,2,Appearance -> "Labeled"}
]

Замена имен параметров на метки

Для этого после обязательного задания начального значения нужно указать название параметра.

Manipulate[Plot[Sin[a x+b],{x,0,2π},PlotRange->{{0,2π},{-1,1}}],
{{a,1,"Частота"},0,2},
{{b,0,"Фаза"},0,2,Appearance -> "Labeled"}
]

Визуализация дискретных значений параметра метками

Для этого после задания дискретного значения нужно поставить стрелочку -> и указать метку (типа string)

Manipulate[Plot[f[a x+b]g[x],{x,0,2π},PlotRange->{{0,2π},{-1,1}}],
{{a,1},0,2},
{{b,1},0,2,0.2},
{{f,Sin},{Sin->"Синус",Cos,Tan->"Тангенс",Cot->"Котангенс"}},
{{g,Sin},{Sin->"Синус",Cos->"Косинус",Tan,Cot->"Котангенс",Sqrt-> "Квадратный корень",Log-> "Логарифм"}}
]

Задание вручную (переопределение заданных по умолчанию) элементов управления

Для этого в описании параметра нужно указать опцию ControlType->ТипУправления, где ТипУправления может принимать одно из следующих значений:
 Animator, Checkbox, CheckboxBar, ColorSetter, ColorSlider, InputField, Manipulator, PopupMenu, RadioButton, RadioButtonBar, Setter, SetterBar, Slider, Slider2D, TogglerBar, Trigger, VerticalSlider, None.

Manipulate[Plot[f[a x+b]g[x],{x,0,2π},PlotRange->{{0,2π},{-1,1}}],
{{a,1},0,2},
{{b,1},0,2,0.2},
{{f,Sin},{Sin,Cos,Tan,Cot},ControlType->Slider},
{{g,Sin},{Sin,Cos,Tan,Cot,Sqrt,Log},ControlType->RadioButton}
]

Такое переопределение не всегда удается

Manipulate[Plot[f[a x+b],{x,0,2π},PlotRange->{{0,2π},{-1,1}}],
{{a,1},0,2},
{{b,1},0,2,0.2},
{{f,Sin},{Sin,Cos,Tan,Cot},ControlType->Slider2D}
]

Использование фунций в качестве элементов контроля

Manipulate[Plot[f[a x+b],{x,0,2π},PlotRange->{{0,2π},{-1,1}}],
{{a,1},0,2},
{{b,1},0,2,0.2},
{{f,Sin},(Which[
pt[[1]]<0.5&&pt[[2]]<0.5,f=Sin,
pt[[1]]<0.5&&pt[[2]]≥ 0.5,f=Cos,
pt[[1]]≥ 0.5&&pt[[2]]< 0.5,f=Tan,
pt[[1]]≥ 0.5&&pt[[2]]≥ 0.5,f=Cot
];Slider2D[Dynamic[pt]])&}
]

Интерактивное создание локаторов

Для этого в описании локатора нужно задать опцию LocatorAutoCreate -> True. Теперь при удерживании клавиши Alt и клике на свободную часть поля мы будем создавать новые локаторы. При удерживании Alt и клике на существующий локатор мы удалим этот локатор.

Manipulate[Graphics[{EdgeForm[{Blue,Thick}],FaceForm[Yellow],Polygon[pt]},PlotRange->2],{{pt,{{0,0},{1,0},{1,1},{0,1}}},Locator,LocatorAutoCreate->True}]

Пример программы рисования многоугольника на плоскости Лобачевского (Alt+Click на свободное поле добавляет вершины). Смещая локаторы, можно наблюдать за соответствующим изменением многоугольника.

g[z_,φ_,z0_]:= (E^(I φ))((z-z0)/(1-z Conjugate[z0]));
ginv[z_,φ_,z0_]:= (E^(-I φ))((z+(E^(I φ)) z0)/(1+z (E^(-I φ)) Conjugate[z0]));

line[z1_,z2_,t_]:=ginv[t g[z2,0,z1],0,z1];
Manipulate[res=p;Show[{
Graphics[Circle[]],
Table[ParametricPlot[With[{z=line[Complex[Sequence@@p[[i]]],Complex[Sequence@@p[[If[i==Length[p],1,i+1]]]],t]},{Re[z],Im[z]}],{t,0,1}],
{i,If[Length[p]==2,1,Length[p]]}]
}],{{p,{{-1/2,0},{1/2,1/2}}},Locator,LocatorAutoCreate->True}
]

Упражнения

Для кривой γ визуализировать ее каустику (множество центров кривизны) и динамику волновых фронтов (эквидистант). Убедиться, что особенности волновых фронтов заметают каустику.

SetAttributes[wave,HoldFirst];
wave[{x_[t_,px_],y_[t_,py_]},{px0_,pxmin_,pxmax_},{py0_,pymin_,pymax_},{tmin_,tmax_},{min_,max_},OptionsPattern[ParametricPlot]]:=
DynamicModule[{γ,s,v,a,n,σ,k,caustic},
Manipulate[
γ[s_]:={x[s,px],y[s,py]};
n[s_]=(({-γ'[s][[2]],γ'[s][[1]]})/(Norm[γ'[s]]));
k[s_]=((Det[{γ'[s],γ''[s]}])/((Norm[γ'[s]])^3)); 
caustic[s_]:=γ[s]+(1/(k[s]))n[s];
Show[{
ParametricPlot[γ[s],{s,tmin,tmax}],
ParametricPlot[caustic[s],{s,tmin,tmax},PlotStyle->Directive[Yellow]],
ParametricPlot[γ[s]+n[s] σ,{s,tmin,tmax},PlotStyle->OptionValue[PlotStyle]]
},PlotRange->OptionValue[PlotRange],AspectRatio-> OptionValue[AspectRatio]
],{{σ,0},min,max},{{px,px0},pxmin,pxmax},{{py,py0},pymin,pymax}, Paneled->False
]
]

wave[{#2  Cos[#1]&[t,px],#2  Sin[#1]&[t,py]},{2,1,5},{3,1,5},{0,2π},{-1,5},{AspectRatio->Automatic,PlotRange->{{-3,3},{-4,4}},PlotStyle->Directive[Red]}]

wave[{#1&[t,px],Sin[#1]&[t,py]},{2,1,5},{3,1,5},{-4π,4π},{-20,20},{AspectRatio->Automatic,PlotRange->{{-4π,4π},{-15,15}}},PlotStyle->Directive[Red]]

wave[{#1+#2 Cos[#1+(π/2)]&[t,px],1+#2 Sin[#1+(π/2)]&[t,py]},{0.5,0,2},{0.5,0,2},{-4π,4π},{-3π,3π},{AspectRatio->Automatic,PlotRange->{{-4π,4π},{-8,8}}},PlotStyle->Directive[Red]]

Смоделировать часы со стрелками.

Dynamic[DateList[],UpdateInterval->1]

DynamicModule[{α,β,γ,δ=0.05,δ1,φ},
Dynamic[
Graphics[
{
Circle[],
Sequence@@Table[If[Mod[i,5]==0,δ1=δ,δ1=δ/2];φ=((2π)/60)i;Line[{Cos[φ],Sin[φ]}#&/@{1-δ1,1+δ1}],{i,0,59}],
Sequence@@Table[φ=((2π)/4)-((2π)/12)i;Text[ToString[i],(1+2δ){Cos[φ],Sin[φ]},Automatic],{i,1,12}],
Yellow,α=((2π)/4)-2π((Round[DateList[][[6]]])/60);Arrow[{{0,0},{Cos[α],Sin[α]}}],
Red,β=((2π)/4)-2π((DateList[][[5]]+DateList[][[6]]/60)/60);Thickness[0.008],Arrow[{{0,0},(1-2δ){Cos[β],Sin[β]}}],
Black,γ=((2π)/4)-2π((Mod[DateList[][[4]],12]+DateList[][[5]]/60)/12);Thickness[0.01],Arrow[{{0,0},(1-6δ){Cos[γ],Sin[γ]}}]
},
PlotRange->{{-1.2,1.2},{-1.2,1.2}},AspectRatio->Automatic],
UpdateInterval->1
]
]