Главная страница

Программирование в Scilab Микаэль Боден Micha


Скачать 1.35 Mb.
НазваниеПрограммирование в Scilab Микаэль Боден Micha
Дата18.09.2022
Размер1.35 Mb.
Формат файлаpdf
Имя файлаprogscilab-v.0.10_ru.pdf
ТипРеферат
#683094
страница10 из 13
1   ...   5   6   7   8   9   10   11   12   13

e r r o r ( msg )
end e n d f u n c t i o n
В следующем примере мы проверяем выходные значения функции mynorm, когда вход- ной аргумент n равен 1, 2, «inf» и неожиданному значению 12.
- - > m y n o r m ([1 2 3 4] ,1)
ans
=
10.
- - > m y n o r m ([1 2 3 4] ,2)
ans
=
5 . 4 7 7 2 2 5 6 71

- - > m y n o r m ([1 2 3 4] , " inf " )
ans
=
4.
- - > m y n o r m ([1 2 3 4] ,12)
! - - e r r o r 1 0 0 0 0
m y n o r m : I n v a l i d v a l u e 12 for n .
at l i n e
10 of f u n c t i o n m y n o r m c a l l e d by :
m y n o r m ([1 2 3 4] ,12)
Входной аргумент функции error является строкой. Мы могли бы использовать более простые сообщения, такие как "Неверное значение n.", например. Наше сообщение немно- го более сложное по нескольким причинам. Цель — дать пользователю полезную обратную связь, когда формируется ошибка в как можно более глубоком звене цепи вызовов. Для то- го, чтобы так сделать, мы даём как можно больше информации о происхождении ошибки.
Во-первых, это удобно для пользователя, что его информируют о том, какое точно значение было отклонено алгоритмом. Следовательно, мы включаем действующее значение входного аргумента n в сообщение. Более того, мы делаем так, чтобы первой строкой сообщения об ошибке было название функции. Это позволяет немедленно получить имя функции, сфор- мировавшей ошибку. В этом случае используется функция msprintf для форматирования строки и формирования переменной msg, которая передаётся функции error.
Мы можем сделать так, что сообщение об ошибке можно было перевести на другие языки, если необходимо. В самом деле, Scilab локализован так, что большинство сообщений появятся на языке пользователя. Следовательно, мы получим сообщения на английском в
США и Великобритании, на французском во Франции и т. д. Для того, чтобы это сделать,
мы можем использовать функцию gettext, которая возвращает переведённую строку, на основе базы данных локализации. Это приведено на следующем примере.
l o c a l s t r = g e t t e x t ( " %s : I n v a l i d v a l u e %d for n . " )
msg = m s p r i n t f ( l o c a l s t r , " m y n o r m " , n )
e r r o r ( msg )
Заметьте, что строка, которая передаётся в функцию gettext, является не выходным,
а входным аргументом функции msprintf. Это из-за того, что система локализации предо- ставляет карту из строки "%s: Invalid value %d for n." в локализованные строки, такие как, например, французское сообщение "%s: Valeur %d invalide pour n.". Эти локализо- ванные сообщения хранятся в файлах данных «.pot». Больше информации о локализации найдёте в [
28
].
Есть случаи, когда мы хотим сформировать сообщение, но не хотим прерывать вы- числение. Например, мы хотим информировать нашего пользователя, что функция mynorm несколько хуже по сравнению со встроенной функцией norm. В этом случае мы не хотим прерывать алгоритм, поскольку вычисление правильное. Мы можем использовать функцию warning, как в следующем примере.
1
f u n c t i o n y = m y n o r m 2 ( A , n )
2
msg = m s p r i n t f ( " %s :
P l e a s e use n o r m i n s t e a d . " , " m y n o r m 2 " )
3
w a r n i n g ( msg )
4
if ( n == 1 ) t h e n
5
y = sum ( abs ( A ))
6
e l s e i f ( n == 2 ) t he n
7
y = sum ( A . ^ 2 ) ^ ( 1 / 2 )
8
e l s e i f ( n == " inf " ) t h e n
9
y = max ( abs ( A ))
10
e l s e
72

11
msg = m s p r i n t f ( " %s : I n v a l i d v a l u e %d for n . " , " m y n o r m 2 " , n )
12
e r r o r ( msg )
13
end
14
e n d f u n c t i o n
Следующий пример показывает результат работы функции.
- - > m y n o r m 2 ([1 2 3 4] ,2)
ВНИМАНИЕ: m y n o r m 2 : P l e a s e use n o r m i n s t e a d .
ans
=
5 . 4 7 7 2 2 5 6 4.3.2
Общая схема для проверок входных аргументов
Устойчивая функция должна защищать пользователя от неправильного использова- ния функции. Например, если функция принимает матрицу значений типа double в качестве входного аргумента, мы можем получить сообщения об ошибке, которые далеко не ясные.
Мы можем даже вообще не получить никаких сообщений от ошибке, например, если функ- ция рушится тихо. В этом разделе мы представляем общую схему для проверок в устойчивых функциях.
Следующая функция pascalup_notrobust — это функция, которая возвращает верх- нюю треугольную матрицу Паскаля P в зависимости от размера матрицы n.
f u n c t i o n P = p a s c a l u p _ n o t r o b u s t ( n )
P = eye ( n , n )
P (1 ,:) = o n e s (1 , n )
for i = 2:( n -1)
P (2: i , i +1) = P ( 1 : ( i -1) , i )+ P (2: i , i )
end e n d f u n c t i o n
Далее мы вычислим верхнюю треугольную матрицу Паскаля размером 5 × 5.
- - > p a s c a l u p _ n o t r o b u s t ( 5 )
ans
=
1.
1.
1.
1.
1.
0.
1.
2.
3.
4.
0.
0.
1.
3.
6.
0.
0.
0.
1.
4.
0.
0.
0.
0.
1.
Функция pascalup_notrobust не делает никаких проверок входного аргумента n. В слу- чае, когда входной аргумент отрицательный или не является числом с плавающей запятой,
функция не формирует никакого сообщения от ошибке.
- - > p a s c a l u p _ n o t r o b u s t ( -1)
ans
=
[]
- - > p a s c a l u p _ n o t r o b u s t ( 1 . 5 )
ans
=
1.
Это поведение не должно считаться «нормальным», поскольку она рушится тихо. Следо- вательно, пользователь может указать неверные входные аргументы функции и даже не заметить что произошло что-то неправильное.
73

Вот почему мы часто проверяем входные аргументы функций так, чтобы формиро- вались как можно более понятные для пользователя сообщения об ошибке. Вообще, мы должны рассматривать следующие проверки:
• число входных/выходных аргументов
• тип входных аргументов,
• размер входных аргументов,
• содержание входных аргументов,
которые могут быть сокращены в виде «число/тип/размер/содержание». Эти правила сле- дует включать как часть нашего стандартного способа написания публичных функций.
4.3.3
Пример устойчивой функции
В этом разделе мы представляем пример устойчивой функции, которая вычисляет мат- рицу Паскаля. Мы представляем примеры, где проверки, которые мы используем, полезны для обнаружения неверного использования функции.
Функция pascalup является улучшенной версией со всеми необходимыми проверками.
f u n c t i o n P = p a s c a l u p ( n )
//
// Проверка числа аргументов
[ lhs , rhs ] = a r g n ()
if ( rhs < > 1 ) t h e n l s t r = g e t t e x t ( " %s : W r o n g n u m b e r of i n p u t a r g u m e n t s : %d to %d e x p e c t e d , but %d p r o v i d e d . " )
e r r o r ( m s p r i n t f ( lstr , " p a s c a l u p " ,1 ,1 , rhs ))
end
//
// Проверка типа аргументов if ( t y p e o f ( n ) < > " c o n s t a n t " ) t h e n l s t r = g e t t e x t ( " %s : W r o n g t y p e for i n p u t a r g u m e n t # %d : %s e x p e c t e d , but %s p r o v i d e d . " )
e r r o r ( m s p r i n t f ( lstr , " p a s c a l u p " ,1 , " c o n s t a n t " , t y p e o f ( n )))
end
//
// Проверка размера аргументов if ( s i z e ( n , " * " ) < > 1 ) t h e n l s t r = g e t t e x t ( " %s : W r o n g s i z e for i n p u t a r g u m e n t # %d : %d e n t r i e s e x p e c t e d , but %d p r o v i d e d . " )
e r r o r ( m s p r i n t f ( lstr , " p a s c a l u p " ,1 ,1 , s i z e ( n , " * " )))
end
//
// Проверка содержания аргументов if ( i m a g ( n ) < >0 ) t h e n l s t r = g e t t e x t ( " %s : W r o n g c o n t e n t for i n p u t a r g u m e n t # %d : c o m p l e x n u m b e r s are f o r b i d d e n . " )
e r r o r ( m s p r i n t f ( lstr , " p a s c a l u p " ,1))
end if ( n < 0 ) t h e n l s t r = g e t t e x t ( " %s : W r o n g c o n t e n t for i n p u t a r g u m e n t # %d : p o s i t i v e e n t r i e s o n l y are e x p e c t e d . " )
e r r o r ( m s p r i n t f ( lstr , " p a s c a l u p " ,1))
end if ( f l o o r ( n ) < > n ) t h e n l s t r = g e t t e x t ( " %s : W r o n g c o n t e n t of i n p u t a r g u m e n t # %d : a r g u m e n t is e x p e c t e d to be a f l i n t . " )
e r r o r ( m s p r i n t f ( lstr , " s p e c f u n _ p a s c a l " ,1))
end
//
P = eye ( n , n )
P (1 ,:) = o n e s (1 , n )
for i = 2:( n -1)
P (2: i , i +1) = P ( 1 : ( i -1) , i )+ P (2: i , i )
end e n d f u n c t i o n
74

В следующем примере мы запускаем функцию pascalup и формируем различные со- общения об ошибках.
- - > p a s c a l u p ( )
! - - e r r o r 1 0 0 0 0
p a s c a l u p : W r o n g n u m b e r of i n p u t a r g u m e n t s : 1 to 1 e x p e c t e d , but 0 p r o v i d e d .
at l i n e
8 of f u n c t i o n p a s c a l u p c a l l e d by :
p a s c a l u p ( )
- - > p a s c a l u p ( -1 )
! - - e r r o r 1 0 0 0 0
p a s c a l u p : W r o n g c o n t e n t for i n p u t a r g u m e n t #1: p o s i t i v e e n t r i e s o n l y are e x p e c t e d .
at l i n e
33 of f u n c t i o n p a s c a l u p c a l l e d by :
p a s c a l u p ( -1 )
- - > p a s c a l u p ( 1.5 )
! - - e r r o r 1 0 0 0 0
s p e c f u n _ p a s c a l : W r o n g c o n t e n t of i n p u t a r g u m e n t #1: a r g u m e n t is e x p e c t e d to be a f l i n t .
at l i n e
37 of f u n c t i o n p a s c a l u p c a l l e d by :
p a s c a l u p ( 1.5 )
Правила, которые мы представили, используются в большинстве макросов Scilab. Мно- гочисленные эксперименты доказали, что эти методы предоставляют улучшенную устойчи- вость, так что пользователи с меньшей вероятностью будут использовать функции с невер- ными входными аргументами.
4.4
Использование parameters
Целью модуля parameters является возможность разработки функций, которые имеют,
возможно, большое количество необязательных параметров. Использование этого модуля позволяет избежать проектирование функции с большим числом входных аргументов, что может привести к путанице. Этот модуль был введён в Scilab версии 5.0, после работы Янна
Коллетта (Yann Collette) по объединению алгоритмов оптимизации, таких как алгоритмы генетики и имитация отжига. Функции модуля parameters представлены на рисунке
24
add_param
Добавить параметр в список параметров get_param
Получить значение параметра из списка параметров init_param
Инициализировать структуру, которая будет управлять списком пара- метров is_param
Проверяет, есть ли в наличии параметр, представленный в списке пара- метров list_param
Перечислить все имена параметров в списке параметров remove_param
Удалить параметр и связанное с ним значение из списка параметров set_param
Установить значение параметра в списке параметров
Рис. 24: Функции из модуля parameters.
В первой части мы делаем обзор модуля и описываем его прямое использование. Во второй части мы представляем практический пример, основанный на алгоритме сортиров- ки. Последняя часть концентрируется на безопасном использовании модуля, защищающем пользователя от общих ошибок.
4.4.1
Обзор модуля
В этом разделе мы представляем модуль parameters и даём пример его использования в контексте алгоритма слияния-сортировки. Точнее, мы представляем функции init_param,
75
add_param и get_param.
Модуль основан на установке связи ключей и значений.
• Каждый ключ соответствует полю списка параметров и сохраняется как строковая переменная. Список доступных ключей определён разработчиком функции. Нет огра- ничений на количество ключей.
• Тип каждого значения зависит от особых требований алгоритма. На самом деле любой тип значения может быть сохранён в структуре данных, включая (но не ограничива- ясь) матрицу чисел типа double, строковые переменные, многочлены и т. д.
Следовательно, эта структура данных чрезвычайно гибка, как мы и увидим далее.
Для того, чтобы понять модуль parameters, мы можем выделить ситуации между двумя точками зрения:
• точка зрения пользователя,
• точка зрения разработчика.
Следовательно, в дальнейшем обсуждении мы будем представлять первую, которая имеет место, когда мы хотим использовать модуль parameters для того, чтобы вызвать функцию.
Затем мы обсудим чт´
о разработчик должен сделать для того, чтобы управлять необязатель- ными параметрами.
Для того, чтобы сделать обсуждение как можно более применимым, мы возьмём при- мер алгоритма сортировки и изучим его в этом разделе. Мы рассматриваем функцию mergesort, которая определяет алгоритм сортировки, основанный на комбинации рекур- сивной сортировки и слияния. Функция mergesort связана со следующими вызывающими последовательностями y = m e r g e s o r t ( x )
y = m e r g e s o r t ( x , p a r a m s )
где x — матрица значений типа double, которые необходимо рассортировать, params — список параметров, и y — матрица рассортированных значений типа double. Действительная реали- зация функции mergesort будет определена позднее в этом разделе. Переменная params —
это список параметров, который определяет два параметра:
• direction, логическое значение, истина для возрастающего порядка и ложь — для убывающего порядка (по умолчанию direction = %t),
• compfun,
функция,
которая определяет функцию сравнения
(по умолчанию compfun = compfun_default).
Функция compfun_default — функция сравнения по умолчанию, будет представлена позднее в этом разделе.
Сначала мы рассмотрим точку зрения пользователя и представим пример использо- вания функций из модуля parameters. Мы хотим отсортировать матрицу чисел в порядке возрастания. В следующем примере, мы вызываем функцию init_param, которая создаёт пустой список параметров params. Затем, мы добавим ключ "direction" к списку пара- метров. Наконец, мы вызовем функцию mergesort с переменной params в качестве второго входного аргумента.
76
p a r a m s = i n i t _ p a r a m ();
p a r a m s = a d d _ p a r a m ( params , " d i r e c t i o n " , %f );
x = [4 5 1 6 2 3] ’;
y = m e r g e s o r t ( x , p a r a m s );
Теперь рассмотрим точку зрения разработчика и проанализируем команды, которые могут быть использованы в теле функции mergesort. В следующем примере мы вызы- ваем функцию get_param для того, чтобы получить значения, соответствующие ключу "direction". Мы передаём значение %t функции get_param, которое является значением,
используемым по умолчанию в случае, когда этот ключ не определён.
- - > d i r e c t i o n = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
d i r e c t i o n
=
F
Поскольку ключ "direction" определён и является ложью, то мы просто получаем назад наше значение. Мы можем использовать расширенную вызывающую последовательность функции get_param с выходным аргументом err. Переменная err равна истине, если во время обработки аргумента произошла ошибка.
- - >[ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
err
=
F
d i r e c t i o n
=
F
В нашем случае никакой ошибки не произошло, поэтому переменная err имеет значение ложь.
Мы можем анализировать другие комбинации событий. Например, давайте рассмотрим ситуацию, когда пользователь определяет только пустой список параметров, как показано ниже.
p a r a m s = i n i t _ p a r a m ();
В этом случае, в теле функции mergesort прямой вызов функции get_param формирует предупреждение.
- - > d i r e c t i o n = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
ВНИМАНИЕ: g e t _ p a r a m : параметр direction не определен d i r e c t i o n
=
T
Предупреждение указывает, что ключ "direction" не определён, так что возвращается зна- чение по умолчанию %t. Если мы используем дополнительный выходной аргумент err, то предупреждений больше нет, но переменная err становится истиной.
- - >[ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
err
=
T
d i r e c t i o n
=
T
Как и в последнем примере, давайте рассмотрим случай, когда пользователь опреде- ляет переменную params как пустую матрицу.
p a r a m s = [];
Как мы увидим позднее в этом разделе, этот случай может произойти когда мы хотим использовать значения по умолчанию. Следующий пример показывает, что ошибка форми- руется при вызове функции get_param.
77

- - > d i r e c t i o n = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
! - - e r r o r 1 0 0 0 0
g e t _ p a r a m : Неверный тип входного параметра №1: ожидался p l i s t .
at l i n e
40 of f u n c t i o n g e t _ p a r a m c a l l e d by :
d i r e c t i o n = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
Действительно, ожидается, что переменная params является plist. Это из-за того, что функ- ция init_param создаёт переменные с типом plist, который стоит для «списка параметров».
Фактически, действительная реализация функции init_param создаёт типизированный спи- сок с типом plist. Для того, чтобы предыдущая ошибка не формировалась, мы можем использовать выходной аргумент err как в следующем примере.
- - >[ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
err
=
T
d i r e c t i o n
=
T
Мы видим, что переменная err — истина, указывающая, что была обнаружена ошибка во время обработки переменной params. Как и прежде, в этом случае функция get_param возвращает значение по умолчанию.
4.4.2
Практический случай
В этом разделе мы рассматриваем действительное применение функции mergesort,
представленной в предыдущем разделе. Алгоритм сортировки, который мы собираемся пред- ставлять, основан на алгоритме, который Бруно Пинсьон (Bruno Pin¸con) разработал в Scilab
[
44
].
Модификации, включённые в реализацию, представленную в этом разделе, включают в себя управление необязательными аргументами "direction" и "compfun". Заинтересован- ные читатели могут найти много подробностей об алгоритме сортировки в [
27
].
Следующая функция mergesort даёт алгоритм сортировки, основанный на методе слияния-сортировки. Её первый аргумент, x — это матрица значений типа double, кото- рые нужно сортировать. Второй аргумент, params, необязательный и представляет список параметров. Мы делаем первый вызов функции get_param для того, чтобы получить зна- чение параметра direction с %t в качестве значения по умолчанию. Затем мы получаем значение параметра compfun с compfun_default в качестве значения по умолчанию. Функ- ция compfun_default определяется позже в этом разделе. Наконец, мы вызываем функцию mergesortre, которая реализует на практике рекурсивный алгоритм слияния-сортировки.
f u n c t i o n y = m e r g e s o r t ( v a r a r g i n )
[ lhs , rhs ]= a r g n ()
if ( and ( rhs < >[1 2] ) ) t h e n e r r m s g = s p r i n t f (..
" %s : U n e x p e c t e d n u m b e r of a r g u m e n t s : " + ..
" %d p r o v i d e d w h i l e %d to %d are e x p e c t e d . " ,..
" m e r g e s o r t " , rhs ,1 ,2);
e r r o r ( e r r m s g )
end x = v a r a r g i n (1)
if ( rhs <2 ) t h e n p a r a m s = []
e l s e p a r a m s = v a r a r g i n (2)
78
end
[ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
[ compfun , err ] = g e t _ p a r a m ( params , " c o m p f u n " , c o m p f u n _ d e f a u l t )
y = m e r g e s o r t r e ( x , d i r e c t i o n , c o m p f u n )
e n d f u n c t i o n
Следующая функция mergesortre реализует рекурсивный алгоритм слияния-сорти- ровки. Алгоритм делить матрицу x на две части x(1:m) и x(m+1:n), где m — целое число в середине между 1 и n. Как только отсортировали с помощью рекурсивного вызова, мы объединяем две упорядоченные части обратно в x.
f u n c t i o n x = m e r g e s o r t r e ( x , d i r e c t i o n , c o m p f u n )
n = l e n g t h ( x )
i n d i c e s = 1 : n if ( n > 1 ) t h e n m = f l o o r ( n /2)
p = n - m x1 = m e r g e s o r t r e ( x (1: m ) , d i r e c t i o n , c o m p f u n )
x2 = m e r g e s o r t r e ( x ( m +1: n ) , d i r e c t i o n , c o m p f u n )
x = m e r g e ( x1 , x2 , d i r e c t i o n , c o m p f u n )
end e n d f u n c t i o n
Заметим, что функция mergesorte не вызывает саму себя. Вместо этого, функция mergesortre, которая на самом деле выполняет алгоритм рекурсивно, имеет фиксированное количество входных аргументов. Это позволяет уменьшить накладные расходы, вызванные обработкой необязательных переменных.
Следующая функция merge объединяет свои два отсортированных входных аргумента x1 и x2 в свой отсортированный выходной аргумент x. Операция сравнения выполняется функцией compfun. В случае, когда аргумент direction — ложь, мы меняем знак пере- менной order, так, чтобы порядок стал «убывающим» вместо «возрастающего» порядка по умолчанию.
f u n c t i o n x = m e r g e ( x1 , x2 , d i r e c t i o n , c o m p f u n )
n1 = l e n g t h ( x1 )
n2 = l e n g t h ( x2 )
n = n1 + n2
x = []
i = 1
i1 = 1
i2 = 1
for i = 1: n o r d e r = c o m p f u n ( x1 ( i1 ) , x2 ( i2 ) )
if (

d i r e c t i o n ) t h e n o r d e r = - o r d e r end if ( order <=0 ) t h e n x ( i ) = x1 ( i1 )
i1 = i1 +1
if ( i1 > m ) t h e n x ( i +1: n ) = x2 ( i2 : p )
b r e a k end e l s e x ( i ) = x2 ( i2 )
i2 = i2 +1 79
if ( i2 > p ) t h e n x ( i +1: n ) = x1 ( i1 : m )
b r e a k end end end e n d f u n c t i o n
Следующая функция compfun_default является функцией сравнения по умолчанию.
Она возвращает order=-1 если xy.
f u n c t i o n o r d e r = c o m p f u n _ d e f a u l t ( x , y )
if ( x < y ) t h e n o r d e r = -1
e l s e i f ( x == y ) t h e n o r d e r = 0
e l s e o r d e r = 1
end e n d f u n c t i o n
Теперь проанализируем поведение функции mergesort вызывая её с особыми входными параметрами. В следующем примере мы сортируем матрицу scivarx, содержащую целый числа с плавающей запятой в порядке возрастания.
- - > m e r g e s o r t ( x ) ’
ans
=
1.
2.
3.
4.
5.
6.
В следующем примере мы устанавливаем параметр direction в %f, так что порядок сорти- рованной матрицы убывающий.
- - > p a r a m s = i n i t _ p a r a m ();
- - > p a r a m s = a d d _ p a r a m ( params , " d i r e c t i o n " , %f );
- - > m e r g e s o r t ( x , p a r a m s ) ’
ans
=
6.
5.
4.
3.
2.
1.
Теперь мы хотим настроить функцию сравнения так, чтобы мы могли менять поря- док сортированной матрицы. В следующем примере мы определяем функцию сравнения mycompfun, которая позволяет разделять чётные и нечётные целые числа с плавающей за- пятой.
f u n c t i o n o r d e r = m y c o m p f u n ( x , y )
if ( m o d u l o ( x ,2) == 0 & m o d u l o ( y ,2) == 1 ) t h e n
// чётное < нечётного o r d e r = -1
e l s e i f ( m o d u l o ( x ,2) == 1 & m o d u l o ( y ,2) == 0 ) t h e n
// чётное > нечётного o r d e r = 1
e l s e
// 1 <3 or 2 <4
if ( x < y ) t h e n o r d e r = -1
e l s e i f ( x == y ) t h e n o r d e r = 0
e l s e o r d e r = 1
end
80
end e n d f u n c t i o n
Мы можем настроить переменную params так, чтобы функция сортировки использовала на- шу настраиваемую функцию сравнения mycompfun вместо функции сравнения по умолчанию compfun_default.
- - > p a r a m s = i n i t _ p a r a m ();
- - > p a r a m s = a d d _ p a r a m ( params , " c o m p f u n " , m y c o m p f u n );
- - > m e r g e s o r t ( [4 5 1 6 2 3] ’ , p a r a m s ) ’
ans
=
2.
4.
6.
1.
3.
5.
Мы не настроили параметр direction, так что был использован порядок по умолчанию
«возрастающий». Как мы можем видеть в выходном аргументе, чётные числа, как ожида- лось, идут перед нечётными.
4.4.3
Проблемы с модулем parameters
У модуля parameters есть проблема, которая может заставить пользователя рассмат- ривать значения по умолчанию вместо ожидаемых. Модуль parameters не защищает поль- зователя от неожиданных полей, что может произойти, если мы сделаем ошибку в имени ключа при настройке параметра. В этом разделе мы покажем как проблема может появить- ся с точки зрения пользователя. С точки зрения разработчика мы представляем функцию check_param, которая позволяет защитить пользователя от использования ключа, который не существует.
В следующем примере мы вызываем функцию mergesort с неверным ключом "compffun"
вместо "compfun".
- - > p a r a m s = i n i t _ p a r a m ();
- - > p a r a m s = a d d _ p a r a m ( params , " c o m p f f u n " , m y c o m p f u n );
- - > m e r g e s o r t ( [4 5 1 6 2 3] ’ , p a r a m s ) ’
ans
=
1.
2.
3.
4.
5.
6.
Мы видим, что наш неправильный ключ "compffun" был проигнорирован, так что использо- валась функция сравнения по умолчанию: матрица была просто отсортирована в возраста- ющем порядке. Причина в том, что модуль parameters не проверяет, что ключ "compffun"
не существует.
Поэтому мы предлагаем использовать следующую функцию check_param, которая при- нимает в качестве входных аргументов список параметров params и матрицу строчных эле- ментов allkeys. Переменная allkeys хранит список всех ключей, которые доступны в спис- ке параметров. Алгоритм проверяет, что каждый ключе в params существует в allkeys. Вы- ходные аргументы — логическая noerr и строковая msg. Если нет ошибки, то переменная noerr — «истина» и строковая msg — пустая матрица. Если один ключ params не найден,
мы устанавливаем noerr в значение «ложь» и формируем сообщение об ошибке. Это со- общение об ошибке используется для действительного формирования ошибки только если выходной аргумент msg не использовался в вызывающей последовательности.
f u n c t i o n [ noerr , msg ] = c h e c k _ p a r a m ( params , a l l k e y s )
if ( t y p e o f ( p a r a m s ) < > " p l i s t " ) t h e n e r r o r ( s p r i n t f (..
g e t t e x t ( " %s : W r o n g t y p e for i n p u t a r g u m e n t # %d : %s e x p e c t e d .\ n " ) , ..
" c h e c k _ p a r a m " , 1 , " p l i s t " ))
81
end if ( t y p e o f ( a l l k e y s ) < > " s t r i n g " ) t h e n e r r o r ( s p r i n t f (..
g e t t e x t ( " %s : W r o n g t y p e for i n p u t a r g u m e n t # %d : %s e x p e c t e d .\ n " ) , ..
" c h e c k _ p a r a m " , 2 , " s t r i n g " ))
end c u r r k e y s = g e t f i e l d (1 , p a r a m s )
n k e y s = s i z e ( c u r r k e ys , " * " )
n o e r r = %t msg = []
// Ключ №1 - это " p l i s t ".
for i = 2 : n k e y s k = f i n d ( a l l k e y s == c u r r k e y s ( i ))
if ( k == [] ) t h e n n o e r r = %f msg = m s p r i n t f (..
g e t t e x t ( " %s : U n e x p e c t e d key " " %s " " in p a r a m e t e r l i s t . " ) ,..
" c h e c k _ p a r a m " , c u r r k e y s ( i ))
if ( lhs < 2 ) t h e n e r r o r ( msg )
end b r e a k end end e n d f u n c t i o n
Следующая версия функции mergesort использует функцию check_param для того,
чтобы проверить, что список доступных ключей является матрицей ["directioncompfun"].
f u n c t i o n y = m e r g e s o r t ( v a r a r g i n )
[ lhs , rhs ]= a r g n ()
if ( and ( rhs < >[1 2] ) ) t h e n e r r m s g = s p r i n t f (..
" %s : U n e x p e c t e d n u m b e r of a r g u m e n t s : " +..
" %d p r o v i d e d w h i l e %d to %d are e x p e c t e d . " ,..
" m e r g e s o r t " , rhs ,1 ,2);
e r r o r ( e r r m s g )
end x = v a r a r g i n (1)
if ( rhs <2 ) t h e n p a r a m s = []
e l s e p a r a m s = v a r a r g i n (2)
end c h e c k _ p a r a m ( params ,[ " d i r e c t i o n " " c o m p f u n " ])
[ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t )
[ compfun , err ] = g e t _ p a r a m ( params , " c o m p f u n " , c o m p f u n _ d e f a u l t )
y = m e r g e s o r t r e ( x , d i r e c t i o n , c o m p f u n )
e n d f u n c t i o n
В следующем примере мы используем, как раньше, неверный ключ "compffun" вместо "compfun".
- - > p a r a m s = i n i t _ p a r a m ();
- - > p a r a m s = a d d _ p a r a m ( params , " c o m p f f u n " , m y c o m p f u n );
- - > m e r g e s o r t ( [4 5 1 6 2 3] ’ , p a r a m s )
! - - e r r o r 1 0 0 0 0
c h e c k _ p a r a m : U n e x p e c t e d key " c o m p f f u n " in p a r a m e t e r l i s t .
82
at l i n e
75 of f u n c t i o n c h e c k _ p a r a m c a l l e d by :
at l i n e
16 of f u n c t i o n m e r g e s o r t c a l l e d by :
m e r g e s o r t ( [4 5 1 6 2 3] ’ , p a r a m s )
С нашей исправленной функцией формируется ошибка, которая позволяет посмотреть нашу ошибку.
4.5
Область видимости переменных в стеке вызовов
В этом разделе мы анализируем область видимости переменных и как это может вза- имодействовать с поведением функций. Поскольку этот момент может привести к неожи- данным ошибкам в программе, то мы предупреждаем об его использовании в функциях,
которые могут быть разработаны лучшим образом. Затем мы представляем два различных случая, где область видимости переменных использовалась без должного внимания и как это может привести к труднообнаружимым ошибкам в программе.
4.5.1
Обзор области видимости переменных
В этом разделе мы представляем область видимости переменных и как это может влиять на поведение функций.
Предположим, что переменная, например a, определена в файле-сценарии и допустим,
что та же переменная используется в функции, вызываемой прямо или косвенно. Что про- изойдёт в этом случае, зависит от чтения первой команды или написания переменной a в теле функции.
• Если первой прочитана переменная a, то значение этой переменной используется на более высоком уровне.
• Если первой написана переменная a, то местная переменная меняется (но переменная на более высоком уровне не меняется).
Представим эту общую схему работы на нескольких примерах.
В следующем примере мы представляем случай, когда переменная первой читается.
Следующая функция f вызывает функцию g и вычисляет выражение, зависящее от входного аргумента x и переменной a.
f u n c t i o n y = f ( x )
y = g ( x )
e n d f u n c t i o n f u n c t i o n y = g ( x )
y = a (1) + a (2 ) * x + a ( 3 ) * x ^2
e n d f u n c t i o n
Заметим, что переменная a не является входным аргументом функции g. Заметим также,
что в этом конкретном случае переменная a только читается, но не пишется.
В следующем примере мы определим переменные a и x. Затем мы вызовем функцию f с входным аргументом x. Когда мы определим значение a, то мы будет на уровне вызова
№0 в стеке вызовов, в то время как в теле функции g мы находимся на уровне вызова №2 в стеке вызовов.
- - > a = [1 2 3];
- - > x = 2;
- - > y = f ( x )
83
y
=
17.
- - > a a
=
1.
2.
3.
Мы видим, что функция f была вычислена правильно, используя в теле функции g, на уровне вызова №2, значение a, определённое на уровне вызова №0. Действительно, когда интерпретатор заходит в тело функции g, переменная a используется, но не определена.
Это из-за того, что она не является ни входным аргументом, ни локально определённой в теле функции g. Следовательно, интерпретатор ищет a на более высоких уровнях в стеке вызовов. В теле функции f, на уровне №1 нет переменной a. Следовательно, интерпретатор ищет на более высоком уровне. На уровне №0 интерпретатор находит переменную a, кото- рая содержит [1 2 3]. Эта переменная используется для вычисления выражения y = a(1)
+ a(2)*x+a(3)*xˆ2, что, наконец, позволяет вернуть переменную y с правильным содержа- нием. Этот процесс представлен на рисунке
25
Уровень №0
a = [1 2 3];
Уровень №1
function y = f ( x )
y = g ( x )
Уровень №2
function y = g ( x )
y = a(1) + a(2)*x + a(3)*x^2
Использование переменной "а"
Поиск переменной "а" на уровне №2... ничего.
Поиск переменной "а" на уровне №1... ничего.
Поиск переменной "а" на уровне №0... найдена!
Рис. 25: Область видимости переменных. – Когда переменная используется, но не определена на нижнем уровне, то интерпретатор ищет ту же переменную на более высоком уровне стека вызовов. Будет использоваться первая переменная, которая будет найдена.
На практике, нам следует изменить построение функции g для того, чтобы сделать использование переменной a более ясным. Следующий пример определяет модифицирован- ную версию функции g, где входной аргумент a делает явным тот факт, что мы используем переменную a.
f u n c t i o n y = g f i x e d ( x , a )
y = a (1) + a (2 ) * x + a ( 3 ) * x ^2
e n d f u n c t i o n
Функция f, соответственно, должна быть обновлена для того, чтобы указать функции gfixed аргумент, который нужен. Это поясняет следующий пример.
84
f u n c t i o n y = f f i x e d ( x , a )
y = g f i x e d ( x , a )
e n d f u n c t i o n
Клиентский исходный код также следует обновить, как в следующем примере.
- - > a = [1 2 3];
- - > x = 2;
- - > y = f f i x e d ( x , a )
y
=
17.
Функции ffixed и gfixed, с точки зрения разработки программного обеспечения, гораздо яснее, чем их предыдущие версии.
Теперь представим случай, где переменная a написана первой. Следующая функция f2 вызывает функцию g2 и вычисляет выражение, зависящее от входного аргумента x и переменной a.
f u n c t i o n y = f2 ( x )
y = g2 ( x )
e n d f u n c t i o n f u n c t i o n y = g2 ( x )
a = [4 5 6]
y = a (1) + a ( 2) * x + a ( 3 ) * x ^2
e n d f u n c t i o n
Заметим, что переменная a написана прежде её использования.
Следующий пример использует те же данные, что и прежде.
- - > a = [1 2 3];
- - > x = 2;
- - > y = f2 ( x )
y
=
38.
- - > a a
=
1.
2.
3.
Заметим, что значение, которое возвращено функцией f2, равно 38, а не предыдущему 17.
Это подтверждает, что использовалось значение локальной переменной a = [4 5 6], опре- делённой на уровне №2, а не переменная a = [1 2 3], определённая на уровне №0. Более того, заметим что переменная a, определённая на уровне №0 не изменилась после вызова f2: её значение по-прежнему a = [1 2 3].
Стек вызовов может быть очень глубоким, но всегда будет то же поведение: какой бы ни был уровень в стеке вызовов, если переменная первой прочитана и была определе- на на более высоком уровне, то переменная будет использоваться напрямую. Это свойство кажется некоторым пользователям удобным. Мы подчёркиваем, что это может привести к программным ошибкам, которые невидимы и их трудно обнаружить. Действительно, раз- работчик функции g может непреднамеренно ошибочно использовать переменную a вместо другой переменной. Это приводит к проблемам, которые представлены в следующем разде- ле.
4.5.2
Плохая функция: неоднозначный случай
В следующих разделах мы представляем случаи, где неверные использования обла- сти видимости переменных ведут к программным ошибкам, которые трудно анализировать.
85

В первом случае мы представляем две вложенные функции, которые производят непра- вильный результат, потому что функция на уровне №2 в стеке вызовов содержит ошибку и использует переменную, определённую на уровне №1. Во втором случае функция, исполь- зующая функцию обратного вызова, тихо рушится, поскольку она использует то же имя переменной, что и пользователь.
Неоднозначный случай представлен в следующем примере ошибки. Мы сначала опреде- ляем функцию f3, которая принимает x и a в качестве входных аргументов, но, неожиданно,
не использует значение a. Вместо этого, выражение содержит переменную b.
f u n c t i o n y = f3 ( x , a )
b = [4 5 6]
y = g3 ( x , a )
e n d f u n c t i o n f u n c t i o n y = g3 ( x , a )
y = b (1) + b (2 ) * x + b ( 3 ) * x ^2
e n d f u n c t i o n
В следующем примере мы определим значение a и x и вызовем функцию f3.
- - > a = [1 2 3];
- - > x = 2;
- - > y = f3 ( x , a )
y
=
38.
- - > e x p e c t e d = a (1) + a ( 2 ) * x + a ( 3 ) * x ^2
e x p e c t e d
=
17.
Значение, которое возвращает функция f3, соответствует параметрам, связанным с перемен- ным b=[4 5 6]. Вместо этого переменная b, используемая в функции g3 была определена в функции f3, на более высоком уровне стека вызовов.
Очевидно, в этом примере функция g3 написана плохо и может содержать программ- ную ошибку. Действительно, мы видим, что входной аргумент a никогда не используется, а b используется, но не является входным аргументов. Вся проблема в том, что, в зависимости от контекста, это может привести к ошибке, а может и не привести: мы на самом деле не знаем.
4.5.3
Плохая функция: случай тихого обрушения
В этом разделе мы представляем типовое обрушение, вызванное неправильным исполь- зование области видимости переменных.
Следующая функция myalgorithm, предоставленная разработчиком, использует фор- мулу производной первого прядка, основанную на прямой конечной разницы. Функция берёт текущую точку scivarx, функцию f и шаг h и возвращает приблизительную производную y.
// От разработчика f u n c t i o n y = m y a l g o r i t h m ( x , f , h )
y = ( f ( x + h ) - f ( x ))/ h e n d f u n c t i o n
Следующая функция myfunction, написанная пользователем, вычисляет многочлен второй степени по прямой формуле. Заметим, что вычисление выходного аргумента y использует точку x, которая является входным аргументом, и параметр a, который не является входным аргументом.
86

// От пользователя f u n c t i o n y = m y f u n c t i o n ( x )
y = a (1) + a (2 ) * x + a ( 3 ) * x ^2
e n d f u n c t i o n
В следующем примере мы устанавливаем a, x, h и вызываем myalgorithm для того, что- бы вычислить численную производную. Мы сравниваем с точной производной и получаем хорошее соответствие.
- - > a = [1 2 3];
- - > x = 2;
- - > h = sq r t ( % e p s );
- - > y = m y a l g o r i t h m ( x , m y f u n c t i o n , h )
y
=
14.
- - > e x p e c t e d = a (2) + 2 * a ( 3 ) * x e x p e c t e d
=
14.
Область видимости переменной a позволяет выполнять функцию myfunction. Действитель- но, переменная a определена пользователем на уровне №0 в стеке вызовов. Когда достигается тело функции myfunction, то интерпретатор находится на уровне №2, где переменная a ис- пользуется, но не определена. Следовательно интерпретатор ищет переменную a на более высоком уровне стека вызовов. Нет такой переменной на уровне №1, поэтому используется переменная a, определённая на уровне №0, производящая, наконец, ожидаемый результат.
Фактически, этот способ использования области видимости переменных является опас- ной практикой программирования. На самом деле, пользователь делает предположение о внутреннем устройстве функции myalgorithm, и эти предположения могут быть ложными,
ведущими к неверным результатам.
Мы слегка изменим функцию, предоставленную разработчиком, и переименуем шаг в a, вместо предыдущего h.
// От разработчика f u n c t i o n y = m y a l g o r i t h m 2 ( x , f , a )
y = ( f ( x + a ) - f ( x ))/ a e n d f u n c t i o n
В следующем примере мы выполняем те же команды, что и прежде.
- - > a = [1 2 3];
- - > x = 2;
- - > h = sq r t ( % e p s );
- - > y = m y a l g o r i t h m 2 ( x , m y f u n c t i o n , h )
! - - e r r o r 21
Неправильный индекс.
at l i n e
2 of f u n c t i o n m y f u n c t i o n c a l l e d by :
at l i n e
2 of f u n c t i o n m y a l g o r i t h m 2 c a l l e d by :
y = m y a l g o r i t h m 2 ( x , m y f u n c t i o n , h )
Ошибка «Неправильный индекс» является следствием того факта, что переменная a была переписана в теле функции myalgorithm2, на уровне №1 в стеке вызовов. Как и ранее,
когда достигается тело функции myfunction на уровне №2 стека вызовов, то интерпретатор ищет переменную a, которая содержит шаг формулы конечной разности. Эта переменная a является матрицей значений типа double только с одной ячейкой. Когда интерпретатор пытается выполнить инструкцию a(1) + a(2)*x + a(3)*xˆ2, это не получается, поскольку
87
ни a(2) ни a(3) не существуют, то есть, целые числа 2 и 3 являются неверными значениями индексов переменной a.
Предыдущая ошибка была, фактически милой. Действительно, она предупреждает нас о том, что что-то неправильное произошло, так что мы можем изменить наш файл-сценарий.
В следующем случае набор команд пройдёт, но с неверным результатом, и это гораздо более опасная ситуация.
Мы снова изменим тело алгоритма, предоставленного разработчиком. В этот раз мы устанавливаем переменную a равной произвольной матрице значений типа double, как в следующей функции myalgorithm3.
// От разработчика f u n c t i o n y = m y a l g o r i t h m 3 ( x , f , h )
a = [4 5 6]
y = ( f ( x + h ) - f ( x ))/ h e n d f u n c t i o n
Тело функции несколько странное, поскольку мы не используем переменную a. но функция остаётся правильной. На самом деле это представляет более сложные случаи, где реализация функции myalgorithm3 использует полный, по возможности широкий, набор переменных. В
этом случае вероятность использования некоторых имён переменных в качестве пользова- теля гораздо выше.
В следующем примере мы вызываем функцию myalgorithm3 и сравниваем с ожидае- мым результатом.
- - > a = [1 2 3];
- - > x = 2;
- - > h = sq r t ( % e p s );
- - > y = m y a l g o r i t h m 3 ( x , m y f u n c t i o n , h )
y
=
29.
- - > e x p e c t e d = a (2) + 2 * a ( 3 ) * x e x p e c t e d
=
14.
Мы видим, что этот набор команд не формирует ошибки. Мы также видим, что ожидаемый результат абсолютно неверен. Как и прежде, переменная a была переопределена на уровне
№1, в теле функции myalgorithm3. Следовательно, когда выполняется выражение a(1) +
a(2)*x + a(3)*xˆ2, то используется значение [4 5 6] вместо матрицы, которую указал пользователь на более высоком уровне.
В нашем конкретном случае легко отладить проблему, поскольку мы имеем точную формулу для производной. На практике мы, вероятно, не будем иметь точную формулу
(вот почему мы используем численные производные. . . ), так что будет гораздо труднее об- наружить и, в случае обнаружения, исправить проблему.
Использование переменной, определённой на более высоком уровне стека вызовов, мо- жет считаться плохой практикой программирования. В случае, когда разработчик функции этого не желал, это может вести к программным ошибкам, которые трудно обнаружить и может оказаться незамеченным долгое время, пока ошибки не исправят.
Используя этот способ, область видимости переменной рассматривается как плохая практика программирования. По-прежнему, может так случиться, что функция требует бо- лее одного входного аргумента, которые надо вычислить. Эта конкретная ошибка приво- дится в разделе
4.6.4
, где мы представляем метод для указания дополнительных входных аргументов функции обратного вызова.
88

4.6
Проблемы с функциями обратного вызова
В этом разделе, мы анализируем характерные проблемы функций обратного вызова.
Сначала мы рассмотрим взаимодействия между именами функций пользователя и разра- ботчика. Мы представляем два различных типа проблем на специальных примерах. Затем мы представляем методы, которые позволяют частично решить эту проблему. Заключении раздела, мы представляем метод, который позволяет управлять функциями обратного вы- зова с дополнительными аргументами. Метод, который мы предлагаем, основан на списках,
которые дают хорошую гибкость, поскольку список может содержать любой другой тип данных.
Мы подчёркиваем, что две первые проблемы, которые мы собираемся представлять в разделах
4.6.1
и
4.6.2
не являются программными ошибками интерпретатора. Как мы увидим, эти проблемы порождены областью видимости переменных, которая была пред- ставлена в предыдущем разделе. Вероятно, что эти проблемы останутся в будущих версиях интерпретатора. Следовательно, стоит проанализировать их детальнее, так чтобы мы могли понять и эффективно решать эти особые проблемы.
4.6.1
Бесконечная рекурсия
В этом разделе мы представляем проблемы, которые порождают бесконечную рекур- сию. Эта проблема случается, когда есть взаимодействие между именем функции, выбран- ным разработчиком и пользователем алгоритма. Поэтому наш анализ должен разделять точки зрения разработчика и пользователя.
Следующая функция myalgorithm, предоставленная разработчиком, берёт переменные x и f в качестве входных переменных и возвращает y. Предполагается, что переменная f является функцией и выражение y=f(x) вычисляется напрямую.
// На уровне разработчика f u n c t i o n y = m y a l g o r i t h m ( x , f )
y = f ( x )
e n d f u n c t i o n
Следующая функция myfunction, предоставленная пользователем, берёт в качестве входного аргумента x и возвращает y. Для того, чтобы вычислить y, пользователь применяет вторую функцию f, которую он написал для этого.
// На уровне пользователя f u n c t i o n y = m y f u n c t i o n ( x )
y = f ( x )
e n d f u n c t i o n f u n c t i o n y = f ( x )
y = x ( 1 ) ^ 2 + x ( 2 ) ^ 2
e n d f u n c t i o n
В следующем примере пользователь устанавливает переменную x и вызывает функцию myalgorithm со входными аргументами x и myfunction.
- - > x = [1 2];
- - > y = m y a l g o r i t h m ( x , m y f u n c t i o n )
! - - e r r o r 26
Слишком сложная рекурсия! (заполнена таблица рекурсии)
at l i n e
2 of f u n c t i o n m y f u n c t i o n c a l l e d by :
at l i n e
2 of f u n c t i o n m y f u n c t i o n c a l l e d by :
at l i n e
2 of f u n c t i o n m y f u n c t i o n c a l l e d by :
[ . . . ]
89

Эта рекурсия, которая в теории бесконечна, фактически конечна, поскольку Scilab разре- шает только ограниченное количество вызовов f.
Причина этой ошибки заключается в конфликте имён функций разработчика и поль- зователя, которые в обоих случаях используют имя переменной f. С точки зрения поль- зователя, функция myfunction просто вызывает f. Но интерпретатор не видит подобного файла-сценария. С точки зрения интерпретатора, символы myfunction и f являются пере- менными, которые хранятся во внутренних структурах данных интерпретатора. Переменная myfunction имеет особый тип данных: она является функцией. То же самое справедливо для пользовательской f. Обе этих переменных определены на уровне №0 в стеке вызовов.
Следовательно, перед вызовом myalgorithm, на уровне №0, функция f определяется пользователем, с телом "y = x(1)ˆ2 + x(2)ˆ2". Когда вызывается myalgorithm, которую мы вводим на уровень №1 и переменная f рассматривается в качестве входного аргумента:
её содержимое f=myfunction. Следовательно, функция f, которая ранее определена поль- зователем, была переписана и потеряла своё первоначальное значение. Интерпретатор вы- полняет выражение y = f ( x ), которое вызывает обратно myfunction. В теле функции myfunction, на уровне №2, интерпретатор находит выражение y = f ( x ). Затем интер- претатор ищет переменную f на текущем уровне в стеке вызовов. Там нет такой переменной.
Следовательно интерпретатор ищет переменную f на более высоком уровне в стеке вызовов.
На уровне №1 переменная f определена с содержанием f=myfunction. Это значение, следова- тельно, используется на уровне №2. Интерпретатор выполняет выражение y = myfunction (
x ) и выходит на уровень №3. Затем интерпретатор ищет переменную f на текущем уровне.
На этом уровне нет такой переменной. Как и прежде, интерпретатор ищет переменную f на более высоком уровне стека вызовов. И снова интерпретатор вызывает функцию f, ко- торая является фактически самой функцией myfunction. Этот процесс повторяется снова и снова до тех пор, пока интерпретатор не достигнет максимального разрешённого размера стека вызовов и сформируется ошибка. Следующий пример показывает последовательность событий.
- - > y = m y a l g o r i t h m ( x , m y f u n c t i o n )
-1 - > f = m y f u n c t i o n
-1 - > y = f ( x )
-1 - > y = m y f u n c t i o n ( x )
-2 - > y = f ( x )
-2 - > y = m y f u n c t i o n ( x )
etc ...
Подчеркнём, что проблема является следствием области видимости переменных в язы- ке Scilab’а. Это напрямую означает следующие возможности языка.
• Функция хранится как любая другая переменная (но с особым типом данных).
• Если переменная неизвестна на уровне №k, то её ищут на более высоком уровне в стеке вызовов до тех пор, пока её или не найдут или не будет сформирована ошибка
(а именно «Неизвестная переменная»).
Заметим, что это не значит, что пользователь знает, что разработчик использовал имя переменной f. Обратное так же верно для разработчика, который не может предугадать,
что пользователь будет использовать переменную f. Заметим, что нет простого способа для пользователя изменить эту ситуацию. Функция myalgorithm может быть предостав- лена внешним модулем. В этом случае смена имени переменной, используемой в функции разработчика может быть невозможным для пользователя. Как мы увидим позднее в этом
90
разделе, пользователь по-прежнему может найти решение на основе небольшой реорганиза- ции исходного кода.
4.6.2
Неправильный индекс
В этом разделе мы представляем ситуацию, где интерпретатор формирует ошибку
«Неправильный индекс» из-за конфликта между именами функций пользователя и раз- работчика.
В следующей функции myalgorithm, предоставленной разработчиком, мы устанавли- ваем локальную переменную f в 1. Затем мы вызываем обратно функцию userf с входным аргументом x и возвращаем выходной аргумент y.
// На уровне разработчика f u n c t i o n y = m y a l g o r i t h m ( x , u s e r f )
f = 1
y = u s e r f ( x )
e n d f u n c t i o n
Заметим, что переменная f установлена, но не используется. Функция по-прежнему годная,
и это не меняет наш анализ.
Следующая функция myfunction, предоставленная пользователем, вызывает функцию f с входным аргументом x и возвращает выходной аргумент y.
// На уровне пользователя f u n c t i o n y = m y f u n c t i o n ( x )
y = f ( x )
e n d f u n c t i o n f u n c t i o n y = f ( x )
y = x ( 1 ) ^ 2 + x ( 2 ) ^ 2
e n d f u n c t i o n
В следующем примере мы устанавливаем переменную x и вызываем функцию myalgorithm.
Это формирует ошибку
«Неправильный индекс»
в теле функции myfunction.
- - > x = [1 2];
- - > y = m y a l g o r i t h m ( x , m y f u n c t i o n )
! - - e r r o r 21
Неправильный индекс.
at l i n e
2 of f u n c t i o n m y f u n c t i o n c a l l e d by :
at l i n e
3 of f u n c t i o n m y a l g o r i t h m c a l l e d by :
y = m y a l g o r i t h m ( x , m y f u n c t i o n )
Объяснение следующее. На уровне №0 в стеке вызова, функция f определена так, как ожидает пользователь. В теле функции myalgorithm мы находимся на уровне №1 в стеке вызовов. Переменная f обновлена командой f=1: теперь f является матрицей значений ти- па double. Затем интерпретатор выполняет выражение y = userf(x). Поскольку userf —
это myfunction, то интерпретатор вызывает функцию myfunction и входит на уровень №2
в стеке вызовов. На этом уровне интерпретатор выполняет выражение y = f ( x ). Затем интерпретатор ищет переменную f на уровне №2: нет такой переменной на этом уровне.
Затем интерпретатор ищет переменную f на более высоком уровне. На уровне №1 интер- претатор находит переменную f, которая является матрицей значений типа double. В этом контексте выражение y = f ( x ) не имеет смысла: переменная x интерпретируется как ин- декс матрицы значений типа double f. Поскольку x=[1 2], то интерпретатор ищет ячейки
91
с индексами 1 и 2 в f. Но f содержит только матрицу значений типа double размером 1 × 1.
Следовательно есть только одна ячейка в f, поэтому формируется ошибка «Неправильный индекс».
Как и в предыдущем разделе, пользователь не может предугадать имена переменных,
выбранных разработчиком, а обратное верно для разработчика.
В следующем разделе мы предлагаем методы решения этих проблем функций обрат- ного вызова.
4.6.3
Решения
В этом разделе мы представляем решения для проблем функций обратного вызова,
которые мы представили ранее. Решение проблем может быть сделано двумя разными спо- собами.
• Изменить функцию разработчика так, чтобы было меньше шансов получить конфликт с именами пользовательских переменных.
• Изменить функцию пользователя так, чтобы он мог использовать функцию разработ- чика.
Очевидно, что мы можем также использовать и тот и другой. В общем, это задача разра- ботчика предоставлять функции, которые достаточно хорошо разработаны, так что пользо- ватель не должен озадачиваться странными программными ошибками. Поэтому мы пред- ставляем это обновление первым. Обновление пользовательской функции представлено во второй часть этого раздела.
Следующая модифицированная функция представляет метод, который разработчик может использовать для написания функции myalgorithm. Мы можем видеть, что второй аргумент __myalgorithm_f__ имеет длинное и сложное имя. Это уменьшает вероятность получения конфликтов между именами переменных пользователя и разработчика.
f u n c t i o n y = m y a l g o r i t h m ( x , _ _ m y a l g o r i t h m _ f _ _ )
y = _ _ m y a l g o r i t h m _ f _ _ ( x )
e n d f u n c t i o n
Это обходной манёвр: по-прежнему есть вероятность, что пользователь получит ошибку.
Действительно, если пользователь выберет имя переменной __myalgorithm_f__, то оста- нется конфликт между именами переменных пользователя и разработчика. Но это весьма маловероятно.
Следующая модифицированная функция представляет метод, который пользователь может использовать для написания функции myfunction. Вместо определения функции f вне тела функции myfunction, мы определяем её внутри. В новой версии переменная f на этот раз определена локально, внутри myfunction. Это ограничивает видимость локальной функции f, к которой может получить доступ myfunction (и более низкие уровни), и она больше не появляется в глобальной области видимости. Следовательно, когда мы вызыва- ем f, то интерпретатор не ищет на более высоких уровнях стека вызовов: переменная f определена на текущем уровне.
f u n c t i o n y = m y f u n c t i o n ( x )
f u n c t i o n y = f ( x )
y = x ( 1 ) ^ 2 + x ( 2 ) ^ 2
e n d f u n c t i o n y = f ( x )
e n d f u n c t i o n
92

В следующем примере мы вызываем функцию myalgorithm с обновлённой функцией myfunction.
- - > x = [1 2];
- - > y = m y a l g o r i t h m ( x , m y f u n c t i o n )
y
=
5.
Как мы можем видеть, теперь проблема решена и мы получаем ожидаемый результат.
4.6.4
Функции обратного вызова с дополнительными аргументами
В этом разделе мы представляем метод, который позволяет решить проблемы, связан- ные с функциями обратного вызова с дополнительными аргументами. Метод, который мы предлагаем основан на списках, которые предоставляют хорошую гибкость, поскольку они могут содержать любой другой тип данных.
Когда мы рассматриваем алгоритм, который берёт функцию обратного вызова в ка- честве входного аргумента, то заголовок функции, которая вызывается обратно, вообще определённо фиксирован, по выбору разработчика алгоритма. Но пользователь алгоритма может захотеть указать функцию, которая не точно совпадает с требуемым заголовком.
Например, алгоритм может ожидать функцию с заголовком y=f(x), а пользователь име- ет функцию, которая требует дополнительный параметр a. Для того, чтобы избежать эту путаницу, созданную использованием области видимости переменных, пользователь может выбрать обновление заголовка функции так, чтобы переменная a была теперь входным аргу- ментом. Это ведёт к заголовку y=g(x,a). В этом случае мы предполагаем, что дополнитель- ный аргумент a является константой, что значит, что алгоритм не изменяет её содержимое.
Простое указание функции g не может работать, поскольку заголовок не совпадает. Для того, чтобы проиллюстрировать эту ситуацию, мы анализируем случай, когда мы хотим вычислить числовую производную.
Следующая функция myderivative1 использует прямую формулу конечной разности для вычисления числовой производной.
f u n c t i o n fp = m y d e r i v a t i v e 1 ( _ _ m y d e r i v a t i v e _ f _ _ , x , h )
fp = ( _ _ m y d e r i v a t i v e _ f _ _ ( x + h ) - _ _ m y d e r i v a t i v e _ f _ _ ( x ))/ h e n d f u n c t i o n
Мы рассмотрим функцию myfun, которая вычисляет косинус многочлена второй степени.
f u n c t i o n y = m y f u n ( x )
y = cos ( 1 + 2 * x +3* x ^2)
e n d f u n c t i o n
В следующем примере мы сравниваем числовую производную с точной производной.
- - > f o r m a t ( " e " ,25)
- - > x = %pi /6;
- - > h = %e p s ^ ( 1 / 2 ) ;
- - > fp = m y d e r i v a t i v e 1 ( m y f u n , x , h )
fp
=
- 1 . 3 8 0 9 7 5 8 5 7 3 7 7 0 5 2 3 0 7 D +00
- - > e x p e c t e d = - sin ( 1 + 2 * x +3* x ^2) * ( 2 + 6 * x )
e x p e c t e d
=
- 1 . 3 8 0 9 7 6 0 3 3 9 7 5 9 5 7 1 4 0 D +00
Поскольку два значения хорошо совпадают, то мы теперь уверены в нашей реализации.
93

Но было бы яснее, если параметры многочлена были сохранены в матрице значений типа double. Это приводит к следующей функции myfun2, которая берёт точку x и параметр a в качестве входных аргументов.
f u n c t i o n y = m y f u n 2 ( x , a )
y = cos ( a ( 1 ) + a ( 2 ) * x + a ( 3 ) * x ^2)
e n d f u n c t i o n
Для того, чтобы управлять данной ситуацией, мы модифицируем реализацию алгорит- ма конечной разности и создадим функцию myderivative2, которая будет подробно рассмот- рена позже в
этом разделе.
В
следующем примере мы вызываем myderivative2 и указываем список list(myfun2,a) в качестве первого аргумента. Функ- ция myderivative2 предполагает, что первый элемент списка — это имя функции, и что остальные элементы списка являются дополнительными аргументами функции, которая бу- дет выполнена. Здесь единственный дополнительный элемент — это a.
- - > x = %pi /6;
- - > a = [1 2 3];
- - > h = %e p s ^ ( 1 / 2 ) ;
- - > fp = m y d e r i v a t i v e 2 ( l is t ( myfun2 , a ) , x , h )
fp
=
- 1 . 3 8 0 9 7 5 8 5 7 3 7 7 0 5 2 3 0 7 D +00
- - > e x p e c t e d = - sin ( a ( 1 ) + a ( 2 ) * x + a ( 3 ) * x ^2) * ( a ( 2 ) + 2 * a ( 3 ) * x )
e x p e c t e d
=
- 1 . 3 8 0 9 7 6 0 3 3 9 7 5 9 5 7 1 4 0 D +00
Следующая функция myderivative2 даёт гибкую реализацию числовой производной,
которая позволяет различать дополнительные аргументы в заголовке функции.
1
f u n c t i o n fp = m y d e r i v a t i v e 2 ( _ _ m y d e r i v a t i v e _ f _ _ , x , h )
2
t y f u n = t y p e o f ( _ _ m y d e r i v a t i v e _ f _ _ )
3
if ( and ( tyfun < >[ " l i s t " " f u n c t i o n " " f p t r " ]) ) t h e n
4
e r r o r ( m s p r i n t f ( " %s : U n k n o w n f u n c t i o n t y p e : %s " ,..
5
" m y d e r i v a t i v e 2 " , t y f u n ))
6
end
7
if ( t y f u n == " l i s t " ) t h e n
8
n i t e m s = l e n g t h ( _ _ m y d e r i v a t i v e _ f _ _ )
9
if ( nitems <2 ) t h e n
10
e r r o r ( m s p r i n t f ( " %s : Too few e l e m e n t s in l i s t : %d " ,..
11
" m y d e r i v a t i v e 2 " , n i t e m s ))
12
end
13
_ _ m y d e r i v a t i v e _ f _ _ f u n _ _ = _ _ m y d e r i v a t i v e _ f _ _ (1)
14
t y f u n = t y p e o f ( _ _ m y d e r i v a t i v e _ f _ _ f u n _ _ )
15
if ( and ( tyfun < >[ " f u n c t i o n " " f p t r " ]) ) t h e n
16
e r r o r ( m s p r i n t f ( " %s : U n k n o w n f u n c t i o n t y p e : %s " ,..
17
" m y d e r i v a t i v e 2 " , t y f u n ))
18
end
19
f x p h = _ _ m y d e r i v a t i v e _ f _ _ f u n _ _ ( x + h , _ _ m y d e r i v a t i v e _ f _ _ (2: $ ))
20
fx = _ _ m y d e r i v a t i v e _ f _ _ f u n _ _ ( x , _ _ m y d e r i v a t i v e _ f _ _ (2: $ ))
21
e l s e
22
f x p h = _ _ m y d e r i v a t i v e _ f _ _ ( x + h )
23
fx = _ _ m y d e r i v a t i v e _ f _ _ ( x )
24
end
25
fp = ( fx p h - fx )/ h
26
e n d f u n c t i o n
В строке №2 мы выясняем тип входного аргумента __myderivative_f__. Если этот ар- гумент не является ни списком ни указателем функции (т. е. примитивом), то мы формируем
94
ошибку. Тогда код рассматривает два случая. Если первый аргумент является списком, то мы проверяем в строках с №8 по №12 число пунктов списка. Затем, в строке №13, мы сохра- ним первый элемент в отдельной переменной, которая предполагается является функцией.
В строках с №14 по №18 мы проверяем тип этой переменной и формируем ошибку, если переменная не является функцией. Затем в строках №19 и №20 мы вычисляем два значения функции fxph и fx. Заметьте, что мы используем выражение __myderivative_f__(2:$) для того, чтобы предоставить дополнительные аргументы вызывающей функции. Из-за особого способа, которым элементы выделяются из списка, это создаёт ряд входных аргументов,
которые требуются заголовком функции, которую вызвали.
На практике метод, который мы уже представили чрезвычайно гибок. Функция может предоставить более одного дополнительного аргумента. Фактически, число дополнитель- ных аргументов может быть уменьшено использованием сбора списка всех необходимых параметров. Действительно, списки могут быть вложены, что позволяет собрать требуемые параметры в единственную дополнительную переменную.
4.7
Мета-программирование: execstr и deff
В этом разделе мы представляем функции, которые позволяют выполнить инструкции,
которые определены как строки. Поскольку эти строки могут формироваться динамически,
мы можем сделать программы, которые имеют уровень гибкости, который не может быть достигнут другими средствами. В первой части мы представляем функцию execstr и даём пример использования этой чрезвычайно мощной функции. Во второй части мы представля- ем функцию deff, которая позволяет динамически создавать функции, основанные на двух строках, содержащих заголовок и тело функции. В заключительной части мы представляем практическое использование функции execstr, где мы объединяем два модуля, которые не могут модифицироваться пользователем и которые не совпадают точно.
Рисунок
26
представляет функции, которые позволяют динамически выполнять ин- струкции, основанные на строках.
deff оперативное определение функции execstr выполнение инструкций в строке evstr выполнение строки
Рис. 26: Функции мета-программирования.
4.7.1
Основное использование execstr
В этом разделе мы представляем функцию execstr.
Функция execstr берёт в качестве своего первого входного аргумента строку, которая содержит правильную инструкцию Scilab’а. Затем функция выполняет инструкцию как если бы это было частью оригинального файла-сценария на том же самом уровне стека вызовов.
В следующем примере мы динамически формируем строку, содержащую инструкцию,
которая отображает строку "foo". Сначала мы определяем переменную s, которая содер- жит строку "foo". Затем мы устанавливаем переменную instr, конкатенируя переменную s со строками disp(" и "). Это выполняется оператором +, который позволяет конкате- нировать свои операнды. Двойные кавычки " дублируются, что делает так, что выражение
95
интерпретируется как символ " внутри целевой строки. Действительно, если эти кавычки не появятся дважды, то это будет интерпретироваться как конец строки: дублирование кавы- чек позволяет «избежать» знака кавычек. Наконец, мы запустим функцию execstr, которая распечатывает "foo".
- - > s = " foo "
s
=
foo
- - > i n s t r = " d i s p ( " " " + s + " " " ) "
i n s t r
=
d i s p ( " foo " )
- - > e x e c s t r ( i n s t r )
foo
Аргумент execstr может содержать любую правильную инструкцию Scilab’а. Напри- мер, мы можем динамически установить новую переменную с динамически созданным содер- жанием. В следующем примере мы создаём строку instr, содержащую инструкцию z=1+2.
Затем мы выполняем это выражение и проверяем, что переменная z действительно установ- лена равной 3.
- - > x = 1
x
=
1.
- - > y = 2
y
=
2.
- - > i n s t r = " z = " + s t r i n g ( x )+ " + " + s t r i n g ( y )
i n s t r
=
z = 1 + 2
- - > e x e c s t r ( i n s t r )
- - > z z
=
3.
В качестве другого примера использования execstr мы можем читать коллекцию фай- лов данных. Предположим, что файл datafile1.txt содержит строки
1 2 3 4 5 6
и файл datafile2.txt содержит строки
7 8 9 10 11 12
Следующий пример позволяет читать эти два файла последовательно. Функция read берёт строку, представляющую файл, содержащий матрицу элементов типа double и количество строк и столбцов в матрице. Набор команд устанавливает переменную data, которая содер- жит список матрицы, содержащей данные, прочитанные в двух файлах.
d a t a = l i s t ();
for k = 1 : 2
i n s t r = " t = r ea d ( " " d a t a f i l e " + s t r i n g ( k ) + " . txt " " ,2 ,3) "
e x e c s t r ( i n s t r )
d a t a ( $ + 1 ) = t end
Следующий пример показывает динамику предыдущего примера.
96
i n s t r
=
t = r e a d ( " d a t a f i l e 1 . txt " ,2 ,3)
d a t a
=
d a t a (1)
1.
2.
3.
4.
5.
6.
i n s t r
=
t = r e a d ( " d a t a f i l e 2 . txt " ,2 ,3)
d a t a
=
d a t a (1)
1.
2.
3.
4.
5.
6.
d a t a (2)
7.
8.
9.
10.
11.
12.
4.7.2
Основное использование deff
Функция deff позволяет динамически определять функцию, из двух строк, содержа- щих заголовок и тело функции.
В следующем примере мы определим функцию myfun, которая принимает матрицу значений типа double x в качестве входного аргумента и возвращает выходной аргумент y.
Сначала мы определяем заголовок функции в виде строки "y=myfun(x)" и устанавливаем переменную header. Затем мы определяем тело функции и, наконец, мы вызываем функцию deff, которая создаёт требуемую функцию.
- - > h e a d e r = " y = m y f u n ( x ) "
h e a d e r
=
y = m y f u n ( x )
- - > b o d y = [
- - > " y (1) = 2* x ( 1 ) + x (2) - x ( 3 ) ^ 2 "
- - > " y (2) = 2* x (2) + x (3) "
- - >]
b o d y
=
! y (1) = 2* x ( 1 ) + x (2) - x ( 3 ) ^ 2
!
!
!
! y (2) = 2* x (2) + x (3)
!
- - > d e f f ( header , b o d y )
Следующий пример показывает, что новая функция myfun может быть использована как любая другая функция.
- - > x = [1 2 3]
x
=
1.
2.
3.
- - > y = m y f u n ( x )
y
=
- 5.
7.
Единственная разница заключается в типе функции, созданной с помощью deff, как пред- ставлено в разделе
4.1.1
. В следующем примере мы показываем, что функция, созданная с помощью deff, имеет тип 13, который соответствует компилированному макросу.
- - > t y p e ( m y f u n )
97
ans
=
13.
- - > t y p e o f ( m y f u n )
ans
=
f u n c t i o n
Если мы добавим аргумент "n" к вызову функции deff, то это скажет интерпретатору создать вместо этого некомпилированный макрос, который производит функцию с типом
11.
- - > d e f f ( header , body , " n " )
W a r n i n g : r e d e f i n i n g f u n c t i o n : m y f u n .
Use f u n c p r o t (0) to a v o i d t h i s m e s s a g e
- - > t y p e ( m y f u n )
ans
=
11.
- - > t y p e o f ( m y f u n )
ans
=
f u n c t i o n
Мы подчёркиваем, что для того, чтобы создать новую функцию, могла бы исполь- зоваться функция execstr вместо deff. Например, следующий набор команд показывает способ определения новой функции с помощью execstr отдельным определением заголов- ка, тела и окончания функции. Эти строки затем конкатенируются в единую инструкцию,
которая, наконец, исполняется для того чтобы определить новую функцию.
h e a d e r = " f u n c t i o n y = m y f u n ( x ) "
b o d y = [
" y (1) = 2* x ( 1 ) + x (2) - x ( 3 ) ^ 2 "
" y (2) = 2* x (2) + x (3) "
]
f o o t e r = " e n d f u n c t i o n "
i n s t r = [
h e a d e r b o d y f o o t e r
]
e x e c s t r ( i n s t r )
4.7.3
Практический пример оптимизации
В этом разделе мы представляем практический случай использования методов, кото- рые мы представили в предыдущих разделах.
Этом примере мы используем функцию optim, которая является программой для по- иска решения числовой оптимизации. Мы представляем ситуацию, когда нам нужна функ- ция для определения функции адаптера (т. е. «клей»), которая соединяет optim с модулем,
предоставляющим набор задач оптимизации. В первом методе, мы определяем функцию адаптера и передаём функцию обратного вызова с дополнительными аргументами в функ- цию optim. Во втором методе, мы используем функцию execstr для создания функции, чьё
тело формируется динамически.
Пример, который мы выбрали, может показаться сложным. Хотя на практике он пред- ставляет практическую задачу разработки программного обеспечения, где могут использо- ваться только нетривиальные решения.
98

Предположим, что нас интересует расчёт характеристик функции нелинейной оптими- зации optim, предоставляемой Scilab. В этом случае, мы можем использовать модуль Atoms uncprb, который предоставляет коллекцию из 23 тестовых задач неограниченной оптимиза- ции, которая известна как коллекция Морэ, Гарбоу и Хильстрома (More, Garbow, Hillstrom).
Для того, чтобы установить этот модуль, мы используем следующую инструкцию:
a t o m s I n s t a l l ( " u n c p r b " )
и перезапускаем Scilab.
Мы подчёркиваем, что мы рассматриваем здесь точку зрения пользователя, который не может модифицировать ни один из этих пакетов, то есть, не может модифицировать ни функцию optim, ни модуль uncprb. Как мы вскоре увидим, нет точного совпадения между заголовком целевой функции, которая требуется для функции optim и заголовок тестовых функций. которые предоставлены модулем uncprb. Точнее, число и тип входных и выходных аргументов, которые требуются для функции optim, не совпадают с числом и типом входных и выходных аргументов, которые предоставляются модулем uncprb. Это не из-за плохого устройства обоих инструментов: фактически невозможно предоставить «универсальный»
заголовок, удовлетворяющий всевозможным нуждам. Следовательно, мы должны создать промежуточную функцию, которая делает «клей» между двумя этими компонентами.
Модуль uncprb предоставляет значение функции, градиент и вектор функции и яко- биан для всех тестовых задач, и предоставляет матрицу Гессе для 18 задач. Точнее, этот модуль предоставляет следующие функции, где nprob — номер задачи от 1 до 23.
[ n , m , x0 ]= u n c p r b _ g e t i n i t f ( n p r o b )
f = u n c p r b _ g e t o b j f c n ( n , m , x , n p r o b )
g = u n c p r b _ g e t g r d f c n ( n , m , x , n p r o b )
Функция uncprb_getinitf возвращает размер n задачи, число m функции и начальное пред- положение x0. Действительно, существующая целевая функция является суммой квадратов m функций. Функция uncprb_getobjfcn возвращает значение целевой функции, при этом функция uncprb_getgrdfcn возвращает градиент.
Функция optim является программой поиска решения нелинейной неограниченной оп- тимизации, предоставляющей несколько алгоритмов для этого класса задач. Она может управлять неограниченными или частично ограниченными задачами. Самая простая ин- струкция вызова функции optim:
[ fopt , x o p t ]= o p t i m ( costf , x0 )
где costf — функция (т. е. целевая) цены, которую нужно минимизировать, x0 — начальная точка (т. е. начальное предположение), fopt — значение минимума функции, а xopt — точка,
которая достигает это значение. Функция цены costf должна иметь заголовок
[ f , g , ind ]= c o s t f ( x , ind )
где x — текущая точка, ind — целое число с плавающей запятой, представляющее что долж- но быть вычислено, f — значение функции, g — градиент. На выходе ind — это флаг, по- сылаемый функцией программе поиска решения оптимизации, например чтобы прервать алгоритм.
Мы должны сначала получить параметры задачи, так, чтобы мы могли установить клей-функцию. В следующем примере мы получаем параметры первой задачи оптимизации.
n p r o b = 1;
[ n , m , x0 ] = u n c p r b _ g e t i n i t f ( n p r o b );
99


1   ...   5   6   7   8   9   10   11   12   13


написать администратору сайта