Программирование в Scilab Микаэль Боден Micha
Скачать 1.35 Mb.
|
Действительно, встроенная функция rand позволяет генерировать случайные числа, и мы, конечно же, не хотим переопределять эту функцию. В данном случае ошибка очевидна, но практические ситуации могут быть гораздо бо- лее сложными. Например, мы можем использовать сложное разветвлённое дерево функций, где функции очень низкого уровня вызывают это предупреждение. Изучая функцию, имею- щую ошибку, и запуская её интерактивно в большинстве случаев можно найти ошибку. Более того, поскольку в Scilab’е есть много существующих функций, то вероятно создание новой программы изначально породит конфликт имён. В любом случае, мы должны исправить 56 эту ошибку без переопределения какой-нибудь существующей функции. Мы рассмотрим этот вопрос более детально в следующем разделе, где мы представляем пример, в котором временное отключение защиты функции действительно необходимо. То есть мы представ- ляем функцию funcprot, которая упомянута в сообщении о предупреждении в предыдущем примере. 4.1.3 Функции — это переменные В этом разделе мы покажем, что функции является переменными и представляем функцию funcprot. Мощная возможность языка в том, что функции являются переменными. Это подра- зумевает то, что мы можем хранить функции в переменных и использовать переменные как функции. В компилируемых языках эта возможность часто называется как «указатели на функцию». В следующем примере мы определяем функцию f. Затем, мы устанавливаем содержи- мое переменной fp равным функции f. Наконец, мы можем использовать функцию fp как обычную функцию. - - > f u n c t i o n y = f ( t ) - - > y = t + 1 - - > e n d f u n c t i o n - - > fp = f fp = [ y ]= fp ( t ) - - > fp ( 1 ) ans = 2. Эта возможность позволяет нам использовать широко распространённый инструмент программирования, известный как «функции обратного вызова» («callback»). Функция обратного вызова — это указатель функции, который может быть сконфи- гурирован пользователем компонента. Как только компонент сконфигурирован, он может производить обратный вызов функции, определённой пользователем так, что алгоритм мо- жет иметь, по крайней мере частично, настраиваемое поведение. Поскольку функции являются переменными, мы можем установить значение перемен- ной несколько раз. Фактически, для того, чтобы защитить пользователей от нежелательного переопределения функций, может появляться сообщение-предупреждение, как показано в следующем примере. Мы начнём с определения двух функций f1 и f2. f u n c t i o n y = f1 ( x ) y = x ^2 e n d f u n c t i o n f u n c t i o n y = f2 ( x ) y = x ^4 e n d f u n c t i o n Затем мы можем установить переменную f дважды, как в следующем примере. - - > f = f1 f = [ y ]= f ( x ) - - > f = f2 Предупреждение : переопределение функции: f . 57 Используйте f u n c p r o t (0) чтобы не выводить это сообщение f = [ y ]= f ( x ) Мы уже видели это предупреждение в разделе 4.1.2 , в случае, где мы пытались пере- определить встроенную функцию. Но в этой ситуации нет причин защищать себя от установ- ки переменной f в новое значение. К сожалению нет у Scilab’а нет возможности различить эти две ситуации. К счастью есть простой способ отключить на время предупреждение. Функция funcprot, представленная на рисунке 21 , позволяет конфигурировать режим за- щиты функций. prot=funcprot() Получить текущий режим защиты функций funcprot(prot) Установить режим защиты функций prot==0 нет сообщений, когда функция переопределена prot==1 предупреждение, когда функция переопределена (по умолчанию) prot==2 ошибка, когда функция переопределена Рис. 21: Функция funcprot. Вообще, не рекомендуется конфигурировать режим защиты функций долговременно. Например, не следует писать команду funcprot(0) в файле запуска .scilab. Это из-за того, что мы не будет получать предупреждения, и, следовательно, можем использовать файлы- сценарии, содержащие ошибки, не зная об этом. Но, для того, чтобы отключить ненужные сообщения предыдущего примера, мы мо- жем временно отключить защиту. В следующем примере мы дублирование режима защиты функции, устанавливаем его временно равным нулю и затем восстанавливаем режим равным дублированному значению. - - > o l d f u n c p r o t = f u n c p r o t () o l d f u n c p r o t = 1. - - > f u n c p r o t (0) - - > f = f2 f = [ y ]= f ( x ) - - > f u n c p r o t ( o l d f u n c p r o t ) 4.1.4 Функции обратного вызова В этом разделе мы представляем метод управления функциями обратного вызова, то есть, мы рассматриваем случай, где входной аргумент функции сам является функцией. Например, мы рассматриваем вычисление численных производных конечными разностями. В следующем примере мы используем встроенную (built-in) функцию derivative для вычисления производной функции myf. Мы сначала определяем функцию myf, которая возводит в квадрат свой входной аргумент x. Затем мы передаём функцию myf функции derivative в качестве обычного аргумента для вычисления численной производной в точке x=2. - - > f u n c t i o n y = myf ( x ) - - > y = x ^2 - - > e n d f u n c t i o n 58 - - > y = d e r i v a t i v e ( myf , 2 ) y = 4. Чтобы понять поведение функций обратного вызова, мы должны реализовать нашу упрощённую функцию численной производной, как это сделано в следующем примере. Наша реализация численной производной основана на разложении функции в ряд Тейлора первого порядка в соседстве с точкой x. Она принимает в качестве входных аргументов функцию f, точку x, где численная производная должна быть вычислена и шаг h, который должен использоваться. f u n c t i o n y = m y d e r i v a t i v e ( f , x , h ) y = ( f ( x + h ) - f ( x ))/ h e n d f u n c t i o n Подчеркнём, что это пример, который не надо использовать на практике, поскольку встроенная функция derivative гораздо более мощная, чем наша, упрощённая функция myderivative. Заметим, что в теле функции myderivative входной аргумент f используется как обыч- ная функция. В следующем примере мы используем переменную myf в качестве входного аргумента функции myderivative. - - > y = m y d e r i v a t i v e ( myf , 2 , 1. e -8 ) y = 4. Мы сделали предположение, что функция f имеет заголовок y=f(x). Нас может уди- вить что будет, если это не так. В следующем примере мы определяем функцию myf2, которая берёт в качестве вход- ных аргументов как x так и a. f u n c t i o n y = m y f 2 ( x , a ) y = a * x ^2 e n d f u n c t i o n Следующий пример показывает что происходит, если мы попытаемся вычислить численную производную функции myf2. - - > y = m y d e r i v a t i v e ( m y f 2 , 2 , s q r t ( %e p s ) ) ! - - e r r o r 4 Неизвестная переменная: a at l i n e 2 of f u n c t i o n m y f 2 c a l l e d by : at l i n e 2 of f u n c t i o n m y d e r i v a t i v e c a l l e d by : y = m y d e r i v a t i v e ( m y f 2 , 2 , s q r t ( % e p s ) ) Мы по-прежнему хотим вычислить численную производную нашей функции, даже ес- ли она имеет два аргумента. Мы увидим в разделе 4.5.1 как обзор переменных может быть использован, чтобы позволить функции myf2 узнать о величине аргумента a. Метод про- граммирования, который мы рассмотрим не считается «чисто программной» практикой. Вот почему мы представим в разделе 4.6.4 метод управления функциями обратного вызова с дополнительными аргументами. 4.2 Разработка гибких функций В этом разделе мы представляем разработку функций с переменным числом входных и выходных переменных. Этот раздел обозревает функцию argn и переменные varargin 59 и varargout. Как мы увидим, указание переменного числа входных аргументов позволяет упростить использование функции, давая значения по умолчанию для аргументов, которые не устанавливаются пользователем. Функции, относящиеся к этому вопросу представлены на рисунке 22 argn Возвращает текущее число входных и выходных аргументов. varargin Список, хранящий входные аргументы. varargout Список, хранящий выходные аргументы. Рис. 22: Функции, имеющие отношение к переменному числу входных и выходных перемен- ных. В первой части мы анализируем простой пример, который позволяет нам видеть в дей- ствии функцию argn. Во второй части мы рассматриваем реализацию функции, которая вычисляет численные производные данной функции. Затем мы описываем как решить про- блему, порождённую упорядоченными входными переменными, с помощью особого исполь- зования синтаксиса пустой матрицы. В заключительной части мы представляем функцию, чьё поведение зависит от типа её входных аргументов. 4.2.1 Обзор функции argn В этом разделе мы делаем обзор функции argn и переменных varargin и varargout. Мы также представляем простую функцию, которая позволяет понять как вы- полняются эти функции. Мы начинаем анализировать основы управления функций с переменным числом вход- ных и выходных аргументов. Типичный заголовок таких функций следующий. f u n c t i o n v a r a r g o u t = m y a r g n d e m o ( v a r a r g i n ) [ lhs , rhs ]= a r g n () Аргументы varargout и varargin являются списками list, которые представляют входные и выходные аргументы. В телу определения функции, мы вызываем функцию argn, которая формирует две следующих выходных переменных. • lhs, число выходных переменных, • rhs, число входных переменных. Переменные varargin и varargout определяются во время вызова функции. Число элемен- тов в этих двух списках следующее. • На входе число элементов в varargin равно rhs. • На входе число элементов в varargout равно нулю. • На выходе число элементов в varargout должно быть lhs. Действительное число элементов в varargin зависит от аргументов, предоставленных поль- зователем. В противоположность этому список varargout всегда пуст на входе, а задача определения его содержимого оставлена на функцию. 60 Давайте теперь рассмотрим следующую функцию myargndemo, которая сделает эту тему более практичной. Функция myargndemo отображает число выходных аргументов lhs, число входных аргументов rhs и число элементов в списке varargin. Затем мы установим количество выходных аргументов в varargout равным 1. f u n c t i o n v a r a r g o u t = m y a r g n d e m o ( v a r a r g i n ) [ lhs , rhs ]= a r g n () m p r i n t f ( " lhs = %d , rhs = %d , l e n g t h ( v a r a r g i n )= %d \ n " ,.. lhs , rhs , l e n g t h ( v a r a r g i n )) for i = 1 : lhs v a r a r g o u t ( i ) = 1 end e n d f u n c t i o n В нашей функции мы просто копируем входные аргументы в выходные аргументы. Следующий пример показывает как функция работает, когда её вызывают. - - > m y a r g n d e m o (); lhs =1 , rhs =0 , l e n g t h ( v a r a r g i n )=0 - - > m y a r g n d e m o ( 1 ) ; lhs =1 , rhs =1 , l e n g t h ( v a r a r g i n )=1 - - > m y a r g n d e m o (1 ,2); lhs =1 , rhs =2 , l e n g t h ( v a r a r g i n )=2 - - > m y a r g n d e m o (1 ,2 ,3); lhs =1 , rhs =3 , l e n g t h ( v a r a r g i n )=3 - - > y1 = m y a r g n d e m o ( 1 ) ; lhs =1 , rhs =1 , l e n g t h ( v a r a r g i n )=1 - - >[ y1 , y2 ] = m y a r g n d e m o ( 1 ) ; lhs =2 , rhs =1 , l e n g t h ( v a r a r g i n )=1 - - >[ y1 , y2 , y3 ] = m y a r g n d e m o ( 1 ); lhs =3 , rhs =1 , l e n g t h ( v a r a r g i n )=1 В предыдущем примере показано, что число элементов в списке varargin равно rhs. Заменим, что минимальное число выходных аргументов равно 1, так что мы всегда имеем lhs > 1. Это свойство интерпретатора Scilab, который заставляет функцию всегда возвращать по крайней мере один выходной аргумент. Подчеркнём, что функция может быть определена как с varargin, так и с varargout, только с varargin или только с varargout. Это решение остаётся за разработ- чиком функции. Функция myargndemo может быть вызвана с любым числом аргументов. На практике не все вызываемые последовательности будут разрешены, так что мы будем должны вво- дить команды ошибки, которые будут ограничивать использование функции. Эта тема будет рассмотрена в следующем разделе. 4.2.2 Практические вопросы Теперь вернёмся к более практичному примеру, который включает в себя вычисле- ние численных производных. Этот пример довольно сложен, но представляет ситуацию, где число входных и выходных аргументов переменное. Функция derivative в Scilab позволяет вычислить первую (якобиан) и вторую (гесси- ан) производные функции со многими параметрами. Для того, чтобы сделать обсуждение этой темы более жизненной, мы рассмотрим проблему внедрения упрощённой версии этой функции. Реализация функции derivative основана на конечных разностях и использует формулы конечных разностей различных порядков (подробнее на эту тему см. в [ 46 ]). 61 Будем полагать, что функция гладкая и что относительная ошибка в вычислении функ- ции равна машинной точности ε ≈ 10 −16 . Общая ошибка, которая связана с формулой ко- нечных разностей, это сумма ошибок усечения (поскольку используется ограниченной число членов в разложении в ряд Тейлора) и ошибки округления (из-за ограниченной точности вы- числений с плавающей запятой в вычислении функции). Следовательно, оптимальный шаг, который минимизирует общую ошибку может быть однозначно вычислен в зависимости от машинной точности. Следующая функция myderivative1 позволяет вычислять первую и вторую производ- ные функции одной переменной. Её входными аргументами являются функция для диф- ференцирования f и вычисления производной в точке x, шаг h и порядок формулы order. Функция обеспечивает прямую формулу первого порядка и центрированную формулу вто- рого порядка. Выходными аргументами являются значение первой производной fp и второй производной fpp. В комментариях функции мы написали оптимальный шаг в качестве па- мятки. f u n c t i o n [ fp , fpp ] = m y d e r i v a t i v e 1 ( f , x , o r d e r , h ) if ( o r d e r == 1 ) t h en fp = ( f ( x + h ) - f ( x ))/ h // h = % e p s ^ ( 1 / 2 ) fpp = ( f ( x +2* h ) - 2* f ( x + h ) + f ( x ) )/ h ^2 // h = % e p s ^ ( 1 / 3 ) e l s e fp = ( f ( x + h ) - f ( x - h ) ) / ( 2 * h ) // h = % ep s ^ ( 1 / 3 ) fpp = ( f ( x + h ) - 2* f ( x ) + f ( x - h ) )/ h ^2 // h = % e p s ^ ( 1 / 4 ) end e n d f u n c t i o n Теперь проанализируем поведение функции myderivative1 на практике. Мы раскроем, что эта функция имеет несколько изъянов и отсутствие гибкости и производительности. Рассмотрим вычисление численной производной функции косинуса в точке x = 0. Определим функцию myfun, которая вычисляет косинус её входного аргумента x. f u n c t i o n y = m y f u n ( x ) y = cos ( x ) e n d f u n c t i o n В следующем примере мы используем функцию myderivative1 для того, чтобы вычислить первую производную косинуса. - - > f o r m a t ( " e " ,25) - - > x0 = %pi /6; - - > fp = m y d e r i v a t i v e 1 ( m y f u n , x0 , 1 , % e p s ^ ( 1 / 2 ) ) fp = - 5 . 0 0 0 0 0 0 0 7 4 5 0 5 8 0 5 9 6 9 D -01 - - > fp = m y d e r i v a t i v e 1 ( m y f u n , x0 , 2 , % e p s ^ ( 1 / 3 ) ) fp = - 5 . 0 0 0 0 0 0 0 0 0 0 2 6 0 9 4 6 8 2 D -01 Точное значение первой производной равно − sin(π/6) = −1/2. Мы видим, что центрирован- ная формула, связанная с порядком order=2, как и ожидалось, более точная, чем формула порядка order=1. В следующем примере мы вызываем функцию myderivative1 и вычисляем первую и вторую производные косинуса. - - > f o r m a t ( " e " ,25) - - > x0 = %pi /6; - - >[ fp , fpp ] = m y d e r i v a t i v e 1 ( m y f u n , x0 , 1 , % e p s ^ ( 1 / 2 ) ) fpp = 62 - 1 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 D +00 fp = - 5 . 0 0 0 0 0 0 0 7 4 5 0 5 8 0 5 9 6 9 D -01 - - >[ fp , fpp ] = m y d e r i v a t i v e 1 ( m y f u n , x0 , 2 , % e p s ^ ( 1 / 3 ) ) fpp = - 8 . 6 6 0 2 9 9 0 1 6 9 0 7 1 0 9 2 3 7 D -01 fp = - 5 . 0 0 0 0 0 0 0 0 0 0 2 6 0 9 4 6 8 2 D -01 Точное значение второй производной fpp равно − cos(π/6) = − √ 3/2 ≈ −8,6602·10 −1 . И снова мы видим, что формула второго порядка более точная, чем формула первого порядка. Мы проверили, что наша реализация верна. Теперь проанализируем разработку функ- ции и введём необходимость в переменном числе аргументов. Функция myderivative1 имеет три недостатка. • Она может делать ненужные вычисления, что является проблемой производительно- сти. • Она может давать неточные результаты, что является проблемой точности. • Она не позволяет использовать значения по умолчанию для order и h, что является проблемой гибкости. Проблема производительности вызвана тем фактом, что два выходных аргумента fp и fpp вычисляются даже если пользователь функции на самом деле не требует второй, необязательный, выходной аргумент fpp. Заметим, что вычисление fpp требует несколько дополнительных вычислений функции в сравнении с вычислениями fp. Это подразумевает, что если требуется только выходной аргумент fp, то выходной аргумент по-прежнему будет вычисляться, что бесполезно. Точнее, если используется вызывающая последовательность: fp = m y d e r i v a t i v e 1 ( f , x , o r d e r , h ) то, даже если fpp не является выходным аргументом, внутри переменная fpp по-прежнему будет вычисляться. Проблема точности вызывается тем фактом, что оптимальный шаг может быть исполь- зован либо для вычисления первой производной, либо для вычисления второй производной, но не обеих. Действительно, оптимальный шаг разный для первой и для второй производной. Точнее, если используется вызывающая последовательность: [ fp , fpp ] = m y d e r i v a t i v e 1 ( m y f u n , x0 , 1 , % e p s ^ ( 1 / 2 ) ) то шаг %epsˆ(1/2) является оптимальным для первой производной fp. Но, если используется вызывающая последовательность: [ fp , fpp ] = m y d e r i v a t i v e 1 ( m y f u n , x0 , 1 , % e p s ^ ( 1 / 3 ) ) тогда шаг %epsˆ(1/3) является оптимальным для второй производной fpp. Во всех случаях мы должны выбирать и не имеем хорошей точности для первой и второй производных. Проблема гибкости вызвана тем фактом, что пользователь должен определять как order, так и h входными аргументами. Проблема в том, что пользователь может не знать какое значение использовать для этих параметров. Это особенно очевидно для параметра h, который связан с проблемами плавающей запятой, которые могут быть совершенно неиз- вестны пользователю. Следовательно, было бы удобно, если мы бы мы могли использовать |