Предотвращение лавины ошибок времени выполнения в Mathematica

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

Одна вещь, которую я нашел полезной - используйте Python-подобные "утверждать" внутренние функции для обеспечения внутренней согласованности. Любые другие советы?

Assert[expr_, msg_] := If[Not[expr], Print[msg]; Abort[], None]

изменить 11/14 Общая причина лавины предупреждения - это когда подвыражение оценивается как "плохое" значение. Это приводит к тому, что родительское выражение оценивается как "плохое" значение, и это "плохое" распространяется до корня. Встроенные оценки, оцененные по пути, замечают плохость и вызывают предупреждения. "Плохо" может означать выражение с неправильным заголовком, список с неправильным числом элементов, отрицательная определенная матрица вместо положительно определенной и т.д. Вообще-то это не соответствует семантике родительского выражения.

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

Наличие отладчика, установленного на "break on Messages", предотвращает лавину ошибок, хотя кажется, что она слишком долгое время, чтобы она включалась

Ответ 1

Как отмечали другие, существует три способа единообразного решения ошибок:

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

Как отметил Samsdram, правильная печать ваших функций очень поможет. Не забывайте о форме : Pattern, поскольку иногда проще выразить некоторые шаблоны в этой форме, например. x:{{_, _} ..}. Очевидно, что если этого недостаточно [t23 > (?) и Condition (/;) - путь. Самдрам это очень хорошо, но я хотел бы добавить, что вы можете создать свой собственный шаблонный тест через чистые функции, например. f[x_?(Head[#]===List&)] эквивалентно f[x_List]. Обратите внимание: скобки необходимы при использовании амперсандовой формы чистых функций.

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

Throw и Catch имеют свое место, но я считаю, что они должны использоваться только внутри, а ваш код должен сообщать об ошибках через Message. Сообщения могут быть созданы так же, как настройка сообщения об использовании. Я считаю, что ключ к когерентной стратегии ошибки можно построить с помощью функций Check, CheckAbort, AbortProtect.

Пример

Примером моего кода является OpenAndRead, который защищает от оставления открытых потоков при прерывании операции чтения следующим образом:

OpenAndRead[file_String, fcn_]:=
Module[{strm, res},
  strm = OpenRead[file];
  res = CheckAbort[ fcn[strm], $Aborted ];
  Close[strm];
  If[res === $Aborted, Abort[], res] (* Edited to allow Abort to propagate *)
]

который до недавнего времени имел использование

fcn[ file_String, <otherparams> ] := OpenAndRead[file, fcn[#, <otherparams>]&]
fcn[ file_InputStream, <otherparams> ] := <fcn body>

Однако это раздражает делать каждый раз.

Здесь belisarius вступает в игру, создавая метод, который вы можете использовать последовательно. К сожалению, его решение имеет фатальный недостаток: вы теряете поддержку средств подсветки синтаксиса. Итак, вот альтернатива, которую я придумал для подключения к OpenAndRead сверху

MakeCheckedReader /: 
    SetDelayed[MakeCheckedReader[fcn_Symbol, symbols___], a_] :=
    Quiet[(fcn[file_String, symbols] := OpenAndRead[file, fcn[#, symbols] &];
           fcn[file_Symbol, symbols] := a), {RuleDelayed::"rhs"}]

который использует

MakeCheckedReader[ myReader, a_, b_ ] := {file$, a, b} (*as an example*)

Теперь проверка определения myReader дает два определения, как мы хотим. Однако в теле функции file следует называть file$. (Я еще не понял, как назвать файл var, как я бы хотел.)

Изменить: MakeCheckedReader работает, фактически не делая ничего. Вместо этого спецификация TagSet (/:) сообщает Mathematica, что когда MakeCheckedReader находится на LHS a SetDelayed, тогда замените его на нужные определения функций. Также обратите внимание на использование Quiet; в противном случае он будет жаловаться на образцы a_ и b_, появляющиеся в правой части уравнения.

Изменить 2: Leonid указал, как использовать file not file$ при определении проверяемого читателя, Обновленное решение выглядит следующим образом:

MakeCheckedReader /: 
    SetDelayed[MakeCheckedReader[fcn_Symbol, symbols___], a_] :=
    Quiet[(fcn[file_String, symbols] := OpenAndRead[file, fcn[#, symbols] &];
           SetDelayed @@ Hold[fcn[file_Symbol, symbols], a]), 
           {RuleDelayed::"rhs"}]

Обоснование изменения объясняется в этом ответе его. Определив myReader, как указано выше, и проверим его определение, получим

myReader[file$_String,a_,b_]:=OpenAndRead[file$,myReader[#1,a_,b_]&]
myReader[file_Symbol,a_,b_]:={file,a,b}

Ответ 2

Я опаздываю на вечеринку с принятым ответом и всеми, но хочу отметить, что определения формы:

f[...] := Module[... /; ...]

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

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

data = {{0, 1}, {1, 2}, {2, 4}, {3, 8}, {4, 15}, {5, 29}, {6, 50}, {7,
     88}, {8, 130}, {9, 157}, {10, 180}, {11, 191}, {12, 196}, {13, 
    199}, {14, 200}};

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

f0[x_] := First @ Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]

f0[100] (* returns 8 *)

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

f0[1000]
error: First::first: {} has a length of zero and no first element.

Сообщение об ошибке является загадочным, в лучшем случае, не дает никаких указаний относительно проблемы. Если эта функция была вызвана глубоко в цепочке вызовов, возможно, произойдет каскад аналогично непрозрачных ошибок.

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

f1[x_] := Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]

f1[100] (* returns {8} *)
f1[1000] (* returns {} *)

Однако существует сильная традиция Mathematica оставлять исходное выражение неизмененным всякий раз, когда функция оценивается с аргументами вне своего домена. Здесь модуль [.../;...] может помочь:

f2[x_] :=
  Module[{m},
    m = Cases[data, {t_, p_} /; p >= x :> t, {1}, 1];
    First[m] /; m =!= {}
  ]

f2[100] (* returns 8 *)
f2[1000] (* returns f2[1000] *)

Обратите внимание, что f2 завершается полностью, если конечным результатом является пустой список, а исходное выражение возвращается неоценимым - достигается простым способом добавления /; условие к окончательному выражению.

Можно решить вопрос о значимом предупреждении, если происходит случай "не найден":

f2[x_] := Null /; Message[f2::err, x] 
f2::err = "Could not find a value for ``.";

При этом изменении будут возвращены те же значения, но в случае "не найден" будет выведено предупреждающее сообщение. Возвращаемое значение Null в новом определении может быть любым - оно не используется.

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

f2[x_] := (Message[f2::err, x]; Abort[])

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

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

Clear[f]
f[x_] := ...
f[x_] := Module[... /; ...]
f[x_] := ... /; ...

Ответ 3

Проблема здесь в основном является одним из типов. Одна функция создает плохой вывод (неправильный тип), который затем подается во многие последующие функции, вызывая множество ошибок. Хотя Mathematica не имеет определенных пользователем типов, как на других языках, вы можете выполнить сопоставление шаблонов по аргументам функций без особых усилий. Если совпадение не выполняется, функция не оценивает и, следовательно, не подает звуковой сигнал с ошибками. Ключевым элементом синтаксиса является "/;" который идет в конце некоторого кода и сопровождается тестом. Некоторый пример кода (и вывод ниже).

Input:
Average[x_] := Mean[x] /; VectorQ[x, NumericQ]
Average[{1, 2, 3}]
Average[$Failed]

Output:
2
Average[$Failed]

Если тест проще, есть еще один символ, который выполняет аналогичное тестирование шаблона "?" и идет сразу после аргумента в объявлении pattern/function. Другой пример приведен ниже.

Input:
square[x_?NumericQ] := x*x
square[{1, 2, 3}]
square[3]

Output:
square[{1, 2, 3}]
9

Ответ 4

Это может помочь определить определение catchall для получения ошибок и сообщить об этом значимым образом:

f[x_?NumericQ] := x^2;
f[args___] := Throw[{"Bad Arguments: ", Hold[f[args]]}]

Таким образом, ваши вызовы верхнего уровня могут использовать Catch [], или вы можете просто дать ему пузыриться:

In[5]:= f[$Failed]

During evaluation of In[5]:= Throw::nocatch: Uncaught Throw[{Bad Args: ,Hold[f[$Failed]]}] returned to top level. >>

Out[5]= Hold[Throw[{"Bad Args: ", Hold[f[$Failed]]}]]

Ответ 5

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

Вот попытка:

funcDef = t_[args___]  :c-:  a_ :> ReleaseHold[Hold[t[args] := 
                         Check[a, [email protected][a]; Abort[]]]];
[email protected];
v[x_, y_] :c-: Sin[x/y] /. funcDef;
?v
v[2, 3]
v[2, 0] 

The: c-: это, конечно, Esc c-Esc, неиспользуемый символ (\ [CircleMinus]), но каждый мог бы сделать.

Вывод:

Global`v
v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]]

Out[683]= Sin[2/3]

During evaluation of In[679]:= Power::infy: Infinite expression 1/0 encountered. >>

During evaluation of In[679]:= Hold[Sin[2/0]]

Out[684]= $Aborted

Мы изменили

       v[x_, y_] := Sin[x/y]

по

       v[x_, y_] :c-: Sin[x/y] /. funcDef;  

Это почти удовлетворяет моему положению.

Изменить

Возможно, также удобно добавить определение "ню" для функции, которая не подвергается проверке ошибок. Мы можем изменить правило funcDef на:

funcDef = 
     t_[args___]  \[CircleMinus] a_ :> 

            {t["nude", args] := a, 

             ReleaseHold[Hold[t[args] := Check[a, [email protected][a]; Abort[]]]]
            };  

чтобы получить для

 v[x_, y_] :c-: Sin[x/y] /. funcDef;  

этот вывод

v[nude,x_,y_]:=Sin[x/y]

v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]]