Главная              Рефераты - Информатика

Научная работа: Программирование и разработка приложений в Maple

Министерство образования Республики Беларусь

УЧРЕЖДЕНИЕ ОБРАЗОВАНИЯ

«ГРОДНЕНСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ

ИМЕНИ ЯНКИ КУПАЛЫ»

МЕЖДУНАРОДНАЯ АКАДЕМИЯ НООСФЕРЫ

Балтийское отделение

В.З. АЛАДЬЕВ, В.К. БОЙКО, Е.А. РОВБА

ПРОГРАММИРОВАНИЕ И РАЗРАБОТКА ПРИЛОЖЕНИЙ В MAPLE

М о н о г р а ф и я

Гродно – Таллинн 2007

Аладьев В.З. Программирование и разработка приложений в Maple: монография /

В.3. Аладьев, В.К. Бойко, Е.А. Ровба.– Гродно: ГрГУ; Таллинн: Межд. Акад. Ноосферы, Балт. отд.– 2007, 458 с., ISBN 978-985-417-891-2 (ГрГУ), ISBN 978-9985-9508-2-1 (МАНУР)

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

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

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

Монография является одним из немногих изданий по программированию в среде пакета Maple, что и определяет ее место среди исследований по программным средствам для ПК, использующих операционную среду Windows. Вместе с тем мобильность пакета позволяет использовать его многими другими популярными платформами. Монография рассчитана на достаточно широкий круг специалистов, использующих в своей профессиональной деятельности ПК для решения задач математического характера, а также на студентов и учащихся, изучающих курс «Основы информатики и вычислительной техники» физико-математических и других естественнонаучных специальностей соответствующих университетов и колледжей.

Настоящая монография представляет собой авторский оригинал-макет мастер-классов по программированию в среде Maple, проведенных в ряде университетов Прибалтики и СНГ. Данное издание может быть полезно пользователям Maple, чьи задачи требуют непосредственного программирования приложений в среде пакета.

Рецензент: доктор физико-математических наук, профессор А.Н. Дудин

ISBN 978-985-417-891-2 (ГрГУ) © Аладьев В.З. (Таллинн)

ISBN 978-9985-9508-2-1 (МАНУР) Бойко В.К., Ровба Е.А. (Гродно), 2007

Maple V, Maple 6, Maple 7, Maple 8, Maple 9, Maple 10 – торговые марки MapleSoft Inc.

Содержание

Предисловие 5

Глава 1. Базовые сведения по Maple-языку пакета 21

1.1. Базовые элементы Maple-языка пакета 23

1.2. Идентификаторы, предложения присвоения и выделения Maple-языка 30

1.3. Средства Maple-языка для определения свойств переменных 42

1.4. Типы числовых и символьных данных Maple-языка пакета 45

1.5. Базовые типы структур данных Maple-языка 51

1.6. Средства тестирования типов данных, структур данных и выражений 80

1.7. Конвертация Maple-выражений из одного типа в другой 89

1.8. Функции математической логики и средства тестирования пакета 93

Глава 2. Базовые управляющие структуры Maple-языка пакета 106

2.1. Предварительные сведения общего характера 106

2.2. Управляющие структуры ветвления Maple-языка (if-предложение) 108

2.3. Циклические управляющие структуры Maple-языка (while_do-предложение) 111

2.4. Специальные типы циклических управляющих структур Maple-языка 115

Глава 3. Организация механизма процедур в Maple-языке пакета 118

3.1. Определения процедур в Maple-языке и их типы 119

3.2. Формальные и фактические аргументы Maple-процедуры 126

3.3. Локальные и глобальные переменные Maple-процедуры 131

3.4. Определяющие параметры и описания Maple-процедур 138

3.5. Механизмы возврата Maple-процедурой результата ее вызова 150

3.6. Средства обработки ошибочных и особых ситуаций в Maple-языке 156

3.7. Расширенные средства Maple-языка для работы с процедурами 167

3.8. Расширение функциональных средств Maple-языка пакета 176

3.9. Иллюстративные примеры оформления Maple-процедур 184

3.10. Элементы отладки Maple-процедур и функций 205

Глава 4. Организация программных модулей в Maple-языке 213

4.1. Вводная часть 213

4.2. Организация программных модулей Maple-языка 216

4.3. Сохранение процедур и программных модулей в файлах 227

Глава 5. Средства Maple-языка для работы с данными и структурами строчного, символьного, списочного, множественного и табличного типов 235

5.1. Средства работы Maple-языка с выражениями строчного и символьного

типов 235

5.2. Средства работы Maple-языка с множествами, списками и таблицами 249

5.3. Алгебраические правила подстановок для символьных вычислений 265

5.4. Средства Maple-языка для обработки алгебраических выражений 273

5.5. Управление форматом вывода результатов вычисления выражений 305

Глава 6. Средства ввода/вывода Maple-языка 311

6.1. Средства Maple-языка для работы с внутренними файлами пакета 311

6.2. Средства Maple-языка для работы с внешними файлами данных 321

6.2.1. Открытие, закрытие и удаление внешних файлов любого типа 327

6.2.2. Средства обработки особой ситуации «конец файла» доступа к файлам 331

6.2.3. Обработка особых и ошибочных ситуаций процедур доступа к файлам 333

6.3. Базовые средства Maple-языка для обеспечения доступа к внешним

файлам данных TEXT-типа 335

6.3.1. Базовые средства доступа к файлам данных на уровне Maple-выражений 340

6.4. Средства Maple-языка для обеспечения доступа к внешним файлам данных

BINARY-типа 343

6.5. Обеспечение форматированного доступа к внешним файлам данных 345

Глава 7. Графические средства Maple-языка пакета 352

7.1. Графическая интерпретация алгебраических выражений и уравнений

в среде Maple-языка 352

7.2. Двухмерное представление функциональных зависимостей и данных

в среде Maple-языка 354

7.3. Трехмерное представление функциональных зависимостей и данных

в среде Maple-языка 373

7.4. Создание графических объектов на основе базовых примитивов 383

Глава 8. Создание и работа с библиотеками пользователя 392

8.1. Классический способ создания Maple-библиотек 393

8.2. Специальные способы создания библиотек пользователя в среде Maple 415

8.3. Создание пакетных модулей пользователя 426

8.4. Статистический анализ Maple-библиотек 436

9. Средства Maple, не рассматриваемые в настоящей книге,

но полезные для приложений 444

Заключение 443

Литература 448

Справка по авторам 453

Предисловие

Системы компьютерной алгебры (СКА) находят все более широкое применение во многих областях науки таких как математика, физика, химия, информатика и т.д., техники, технологии, образовании и т.д. СКА типа Maple, Mathematica, MuPAD, Macsyma, Reduce, Axiom и Magma становятся все более популярными для решения задач преподавания математически ориентированных дисциплин, как в научных исследованиях, так и в промышленности. Данные системы являются мощными инструментами для ученых, инженеров и педагогов. Исследования на основе СКА-технологии, как правило, сочетают алгебраические методы с продвинутыми вычислительными методами. В этом смысле СКА – междисциплинарная область между математикой и информатикой, в которой исследования сосредотачиваются как на разработке алгоритмов для символьных (алгебраических) вычислений и обработки на компьютерах, так и на создании языков программирования и программной среды для реализации подобных алгоритмов и базирующихся на них проблем различного назначения.

В серии наших работ [1-20,22-33,39,41-46,47,49,50,91,103] довольно детально рассмотрены такие математические пакеты как Maple, Reduce, MathCAD, Mathematica. При этом, особое внимание нами уделелялось особенностям каждого из пакетов, его преимуществам, а также недостаткам, эффективным приемам и методам программирования в его среде, созданию набора средств, расширяющих его возможности, а также выработке системы предложений по его дальнейшему развитию. Наш опыт апробации и использования четырех математических пакетов Mathematica, Reduce, Maple, MathCAD в различных математических и физических приложениях позволяет нам рассматривать именно пакеты Maple и Mathematica в качестве бесспорных лидеров (на основе специального обобщенного индекса) среди всех известных на сегодня современных СКА. При этом, мы предпочитаем именно пакет Maple (несмотря на все его недостатки и недоработки) из-за целого ряда преимуществ, среди которых особо следует выделить такие как развитые графические средства, достаточно эффективные средства решения систем дифференциальных уравнений, средства создания графических интерфейсов пользователя, мощная библиотека математических функций, большой набор сопутствующих пакетных модулей для различных приложений, современный встроенный язык программирования (4GL) интерпретирующего типа, интерфейс с рядом других Windows-приложений, перспективная концептуальная поддержка.

Исследователи используют пакет Maple как важный инструмент при решении задач, связанных с их исследованиями. Пакет идеален (по нынешним понятиям) для формулировки, решения и исследования различных математических моделей. Его алгебраические средства существенно расширяют диапазон проблем, которые могут быть решены на качественном уровне. Педагоги в средних школах, колледжах и университетах обновляют традиционные учебные планы, вводя задачи и упражнения, которые используют диалоговую математику и физику Maple. Тогда как студенты могут сконцентрироваться на важных концепциях, а не на утомительных алгебраических вычислениях и преобразованиях. Наконец, инженеры и специалисты в промышленности используют Maple как эффективный инструмент, заменяющий много традиционных ресурсов типа справочников, калькуляторов, редакторов, крупноформатных таблиц и языков программирования. Эти пользователи легко решают весьма широкий диапазон математически ориентированных задач, разрабатывая проекты и объединяя результаты (как числовые, так и графические) их вычислений в профессиональные отчеты довольно высокого качества.

Между тем, наш эксплуатационный опыт в течение 1997–2006 г.г. с пакетом Maple релизов 4 – 10 позволил нам не только оценить его преимущества по сравнению с другими подобными пакетами, но также выявил ряд ошибок и недостатков, устраненных нами. Кроме того, пакет Maple не поддерживал ряд достаточно важных процедур обработки информации, алгебраических и численных вычислений, включая средства доступа к файлам данных. Ввиду сказанного, в процессе работы с пакетом Maple мы создали достаточно много эффективного программного обеспечения (процедуры и программные модули), целым рядом характеристик расширяющих базовые и по выбору возможности пакета. Данное программное обеспечение было организовано в виде Библиотеки, которая является структурно подобной главной библиотеке Maple и обеспечена развитой справочной системой, аналогичной подобной системе пакета Maple и весьма органично с ней связанной. Комментированное описание данной Библиотеки представлено в нашей книге [103]. К ней же прилагается данная Библиотека версии 2.1810, тогда как обновленную версию данной Библиотеки можно бесплатно загружать с любого из адресов, указанных в [109]. Демонстрационная же версия Библиотеки находится по адресу [108].

При этом, программные средства, составляющие Библиотеку, в своем большинстве имеют дело именно с базовой средой Maple, что пролонгирует их актуальность как на текущие релизы, начиная с шестого, так и на последующие релизы пакета. В этой связи здесь уместно обратить внимание на один весьма существенный момент. При достаточно частом объявлении о новой продукции MapleSoft, между тем, уделяет недостаточно внимания устранению имеющихся ошибок и дефектов, переходящих от релиза к релизу. Некоторые из них являются достаточно существенными. Мы отмечали данное обстоятельство в наших книгах неоднократно, этому вопросу посвящен целый ряд замечаний и членов MUG (Maple Users Group). Более того, расширению инструментальных средств основной среды пакета также уделяется недостаточное внимание, что особенно заметно в режиме продвинутого программирования в его среде. Представленная в [103] Библиотека содержит расширения инструментальных средств, прежде всего, базовой среды пакета, что пролонгирует их актуальность и на последующие релизы пакета, а также весьма существенно упрощает программирование целого ряда задач в его среде и обеспечивает более высокий уровень совместимости релизов 6 - 10. Выявленная нами несовместимость пакета как на уровне релизов, так и на уровне базовых операционных платформ – Windows 98SE и ниже, с одной стороны, и Windows ME/2000/XP и выше, с другой стороны, потребовала решения проблемы совместимости и для средств нашей Библиотеки относительно релизов 6 - 10.

В заключение данной преамбулы вкратце изложим (адресуясь, прежде всего, к нашим достаточно многочисленным читателям как настоящим, так и будущим) наше личное мнение по сравнительной оценке пакетов Maple и Mathematica. Как один, так и другой пакеты изобилуют многочисленными ошибками (в целом ряде случаев недопустимыми для систем подобного рода), устранению которых разработчиками как MapleSoft Inc., так и Wolfram Research уделяется сравнительно небольшое внимание. Из коммерческих соображений часто весьма необоснованно выпускаются новые релизы, сохраняющие старые ошибки и привнося в ряде случаев как новые ошибки, так и различного рода экзотические излишества. Данный вопрос неоднократно поднимался как в наших изданиях, так и перед разработчиками. Однако, если разработчики Maple в режиме открытого диалога с пользователями в какой-то мере пытаются решить данную проблему, то Wolfram Research достаточно болезненно воспринимает любую (в подавляющем большинстве обоснованную) критику в свой адрес. При этом, Wolfram Research ведет весьма агрессивную маркетинговую политику, не вполне адекватную качеству ее продукции. Именно это, прежде всего, объясняет ее временные количественные преимущества, которые достаточно быстро уменьшаются. Сравнивая отклики пользователей пакетов Maple и Mathematica, а также в свете нашего многолетнего опыта работы с обоими пакетами, можно вполне однозначно констатировать, что вторые при использовании пакета имеют значительно больше проблем.

Из нашего опыта достаточно глубокого использования и апробирования обоих пакетов отметим, что Maple – существенно более дружелюбная и открытая система, использующая достаточно развитый встроенный язык 4-го поколения интерпретирующего типа, что упрощает освоение пакета пользователю, имеющему опыт современного программирования. Тогда как пакет Mathematica имеет несколько архаичный и не столь изящный язык, в целом ряде отношений отличный от современных языков программирования. Наконец, Maple имеет по ряду показателей более развитые инструментальные средства (например, для решения диффуравнений в частных производных, предоставления используемого алгоритма решения задачи, настройки графического интерфейса пользователя на конкретные приложения и др.), а также весьма широкий спектр бесплатных приложений во многих фундаментальных и прикладных областях современного естествознания.

Пакет Maple воплощает новейшую технологию символьных вычислений, числовых вычислений с произвольной точностью, наличие инновационных Web-компонент, расширяемой технологии пользовательского интерфейса (Maplets), и весьма развитых математических алгоритмов для решения сложных математических задач. В настоящее время пакет использует более 5 миллионов студентов, ученых, исследователей и специалистов из различных областей. Практически каждый ведущий университет и научно-исследовательский институт в мире, включая такие, как MIT, Cambridge, Stanford, Oxford, Waterloo и др., используют пакет для учебных и исследовательских целей. В промышленных целях пакет используется такими ведущими корпорациями как Boeing, Bosch, Canon, Motorola, NASA, Toyota, Hewlett Packard, Sun, Microsystems, Ford, General Electric, Daimler-Chrysler и др.

Резюмируя сказанное (более детальный сравнительный анализ обоих пакетов можно найти в серии наших работ [1-20,22-33,39,41-46,47,49,50,91,103]), начинающему пользователю систем компьютерной алгебры рекомендуем все же пакет Maple, как наиболее перспективное средство в данной области компьютерной математики. Этому существенно способствует и творческий альянс MapleSoft с всемирно известным разработчиком математического ПО – NAG Ltd. И это при том, что последний имеет и свою довольно приличную СКА – AXIOM, являющуюся на сегодня лидером среди СКА на европейском уровне. При этом, Maple постоянно отвоевывает позиции у Mathematica и начинает доминировать в образовании, что весьма существенно с ориентацией на перспективу; используемая Mapleидеология занимает все более существенное место при создании различных электронных материалов математического характера.

Вместе с тем, современное развитие пакета Maple вызывает и ряд серьезных опасений, которые в общих чертах можно определить следующим образом. Качество любого программного обеспечения определяется в соответствии с большим количеством характеристик, среди которых можно отметить такие существенные как: (1) совместимость программных средств «снизу-вверх», (2) устойчивость функционирования относительно операционных платформ, наряду с качественной поддержкой и сопровождением, и т. д. Данным критериям последние релизы пакета Maple, начиная с 7-го, удовлетворяют все меньше и меньше, а именно.

Достаточно существенные ошибки и недоработки (многие из них неоднократно отражались в наших книгах и статьях, а также в ряде других источниках, включая многочисленные форумы по Maple) переходят от релиза к релизу. Отсутствует совместимость релизов пакета Maple «снизу-вверх». О несовместимости релизов Maple мы неоднократно отмечали в своих книгах и статьях. Кое-что для усовершенствования совместимости нами было сделано (в частности, посредством нашей Библиотеки, представленной в [103,109]), однако не все. Тем временем, для Maple релизов 9 и 10 была обнаружена несовместимость уже среди их клонов. Как хорошо известно, Maple 9 и 10 поддерживают два режима – классический (например, для Maple 9 ядро “cwMaple9.exe”и для Maple 10 ядро “cwMaple.exe”) и стандартный (например, для Maple 9 ядро `Maplew9.exe` и для Maple 10 ядро Maplew.exe). Оказывается, что данные клоны несовместимы даже на уровне встроенных функций.

В частности, если в классическом режиме встроенная функция system выполняется корректно, то в стандартном режиме, возвращая код завершения 0, она некорректно выполняет некоторые команды (программы) MS DOS. По этой причине процедуры нашей Библиотеки, использующие данную функцию и отлаженные в Maple релизов 8 и ниже, а также в классическом режиме Maple 9-10, в стандартном режиме Maple 9-10 выполняются некорректно, часто вызывая непредсказуемые ошибочные ситуации. В целях устранения подобных ситуаций нами была определена процедура System, заменяющая указанную стандартную функцию system и устраняющая ее основные недостатки [103]. Естественно, подобные нарушения требований к качественному программному обеспечению не допустимы для программных средств подобного типа и могут вести к нежелательным для Maple последствиям. Более того, нам кажется, что их действие уже начинает сказываться.

Ввиду сказанного, упомянутая наша Библиотека корректно работает с Maple релизов 6 - 8 и Maple 9 - 10 (классический режим), тогда как для Maple 9 - 10 (стандартный режим) некоторые библитечные средства, использующие стандартную функцию system, будут выпоняться некорректно или вызывать ошибочные ситуации, в ряде случаев непредсказуемые. В этой связи заинтересованный читатель в качестве достаточно полезного упражнения имеет хорошую возможность использовать процедуру System для обновления упомянутых процедур Библиотеки на предмет расширения сферы их применимости и на стандартный режим Maple. В целях большей информативности приведем краткую характеристику нашей Библиотеки [103,108,109].

Характеристика нашей библиотеки программных средств . Упоминаемая здесь Библиотека расширяет диапазон и эффективность использования пакета Maple на платформе Windows благодаря содержащимся в ней средствам в трех основных направлениях: (1) устранение ряда основных дефектов и недостатков, (2) расширение возможностей ряда стандартных средств, и (3) пополнение пакета новыми средствами, расширяющими возможности его программной среды, включая средства, повышающие уровень совместимости релизов 6 - 10 пакета, о которой говорилось выше. Основное внимание было уделено дополнительным средствам, созданным нами в процессе использования пакета Maple релизов 410, которые по целому ряду параметров существенно расширяют возможности пакета и облегчают работу с ним. Значительное внимание уделено также средствам, обеспечивающим повышение уровня совместимости пакета релизов 6-10. Большой и всесторонний опыт использования данного программного обеспечения подтвердил его высокие эксплуатационные характеристики при использовании пакета Maple в многочисленных приложениях, потребовавших не только стандартных средств, но и программирования своих собственных, ориентированных на конкретные приложения.

Со всей определенностью следует констатировать, что серия наших книг по Maple [29-33, 39,41-46,91,103], представляющая разработанные нами средства и содержащая предложения по дальнейшему развитию пакета, в значительной степени стимулировала появление таких полезных приложений как пакетные модули ListTools, FileTools, StringTools и LibraryTools. Между тем, и в свете данных приложений средства нашей Библиотеки существенно расширяют возможности пакета, во многих случаях перекрывая средства указанных пакетных модулей. Текущая версия Библиотеки содержит набор средств (более 730 процедур и программных модулей), ориентируемых на следующие основные виды обработки информации и вычисления [103,108,109]:

1. Программные средства общего назначения

2. Программные средства для работы с процедурными и модульными объектами

3. Программные средства для работы с числовыми выражениями

4. Программныее средства для работы со строчными и символьными выражениями

5. Программные средства для работы со списками, множествами и таблицами

6. Программное обеспечение поддержки структур данных специального типа

7. Программное обеспечение для по-битной обработки информации

8. Программные средства, расширяющие графические возможности пакета

9. Расширение и исправление стандартного программного обеспечения Maple

10. Программное обеспечение для работы с файлами данных

10.1. Программное обеспечение общего назначения

10.2. Программное обеспечение для работы с текстовыми файлами

10.3. Программное обеспечение для работы с бинарными файлами

10.4. Программное обеспечение для работы с файлами Maple

10.5. Специальное программное обеспечение для работы с файлами данных

11. Программное обеспечение для решения задач математического анализа

12. Программное обеспечение для решения задач линейной алгебры

12.1. Программное обеспечение общего назначения

12.2. Программное обеспечение для работы с rtable-объектами

13. Программное обеспечение для решения задач простой статистики

13.1. Программное обеспечение для решения задач описательной статистики

13.2. Программное обеспечение для решения задач регрессионного анализа

13.3. Программное обеспечение для проверки статистических гипотез

13.4. Элементы анализа временных (динамических) и вариационных рядов

14. Программное обеспечение для работы с библиотеками пользователя

Основные новации нашей Библиотеки с привязкой к вышеперечисленным разделам, тематически классифицирующим средства Библиотеки, кратко охарактеризованы в Предисловии к нашей книге [103] и на web-страницах www.aladjev.narod.ru/MapleBook.htm и www.exponenta.ru/educat/news/aladjev/book2.asp. Исходя же из нашего многолетнего опыта использования пакета Maple релизов 4 - 10 и опыта наших коллег из университетов и академических институтов России, Эстонии, Белоруссии, Литвы, Латвии, Украины, а также ряда других стран, следует отметить, что многие из средств (или их аналоги) нашей Библиотеки весьма целесообразно включить в стандартные поставки последующих релизов пакета Maple. Соответствующие предложения были нами представлены разработчикам пакета. При этом, можно констатировать, что ряд наших книг по Mapleпроблематике, которые представляют средства, разработанные нами, и содержат полезные рекомендации по дальнейшему развитию пакета, стимулировали появление модулей FileTools, ListTools, LibraryTools и StringTools. Однако, в этом отношении средства, представленные нами, существенно расширяют возможности пакета, во многих случаях превышая таковые из указанных пакетных модулей. В настоящее время они доступны пользователям Maple в виде предлагаемой Библиотеки, функционирующей на платформах Windows и поддерживающей релизы 6-10 пакета. Данная Библиотека прилагается к нашей книге [103], а также может быть получена по адресу [109]. Средства Библиотеки в целом ряде случаев позволяют существенно упрощать программирование различных прикладных задач в среде пакета Maple релизов 6 - 10. Настоящая Библиотека была отмечена в 2004 г. наградой Smart Award от Smart DownLoads Network.

Программные средства, предоставляемые данной Библиотекой, снимают целый ряд вопросов, возникших в дискуссиях членов группы пользователей Maple (MUG) на целом ряде форумов по Maple, и существенно расширяют функциональные возможности пакета, облегчая его использование и расширяя сферу приложений. Библиотека предназначена для достаточно широкой аудитории ученых, специалистов, преподавателей, аспирантов и студентов естественно научных специальностей, которые в своей профессиональной работе используют пакет Maple релизов 6 - 10 на платформе Windows. Библиотека содержит оптимально разработанное, интуитивное программное обеспечение (набор процедур и программных модулей), которое достаточно хорошо дополняет уже доступное программное обеспечение пакета с ориентацией на самый широкий круг пользователей, в целом ряде случаев расширяя сферу применения пакета и его эффективность.

Библиотека структурно подобна главной библиотеке Maple, снабжена развитой справочной системой по средствам, расположенным в ней, и логически связана с главной библиотекой пакета, обеспечивая доступ к средствам, содержащимся в ней, подобно стандартным средствам пакета. Простое руководство описывает установку Библиотеки при наличии на компьютере с платформой Windows инсталлированного пакета Maple релизов 6, 7, 8, 9, 9.5 и/или 10. Для полной установки данной Библиотеки требуется 17 МB свободного пространства на жестком диске. Сопутствующие материалы содержат немало дополнительной полезной информации, которая по тем либо иным причинам не была включена в основной текст книги.

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

Следует отметить, что поставляемые с Библиотекой файлы “ProcUser.txt” (для Maple 6–9) и “ProcUser10.txt” (для Maple 9.5/10), содержащие исходные тексты программных средств, составляющих Библиотеку, а также полный набор mws-файлов с help-страницами, составляющими справочную базу Библиотеки, наряду с большим набором различного назначения примеров, позволяют достаточно легко адаптировать Библиотеку на базовые платформы, отличные от Windows-платформы. Более того, в виду наследования встроенными языками математических пакетов целого ряда общих черт, имеется хорошая возможность адаптации ряда процедур нашей Maple-библиотеки к программной среде других пакетов. В частности, целый ряд процедур Библиотеки достаточно легко был нами адаптирован к среде пакета Mathematica и некоторых других математических пакетов, тогда как предложенный нами метод “дисковых транзитов”, существенно расширяющий возможности программирования, эффективен не только для математических пакетов. При этом, следует иметь в виду, что исходные тексты программных средств, представленные в книге [103], и их представления в нашей Библиотеке (будучи функционально эквивалентными) могут в определенной степени различаться, что обусловлено широким использованием Библиотеки также и в учебных целях.

Наш и опыт наших коллег показывает, что использование Библиотеки в целом ряде случаев существенно расширяет возможности пакета Maple релизов 6 – 10 и последующих релизов, упрощая программирование различных прикладных задач в его среде. Данная Библиотека представит особый интерес прежде всего для тех, кто использует пакет Maple не только как высоко интеллектуальный калькулятор, но также и как среду программирования различных задач математического характера в своей профессиональной деятельности.

Библиотека в совокупности с главной Maple-библиотекой обладает полнотой в том отношении, что любое ее средство использует или средства главной библиотеки и/или средства самой Библиотеки. В этом плане она полностью самодостаточна. Ряд часто используемых процедур Библиотеки, ориентированных на массовое применение при программировании различных приложений, оптимизирован. Тогда как многие, обладая функциональной полнотой, на которую они и были ориентированы, между тем, в полной мере не оптимизированы, что предоставляет пользователю (прежде всего серьезно осваивающему программирование в Maple) достаточно широкое поле для его творчества как по оптимизиции процедуры, так и по созданию собственных аналогов, постоянно контролируя себя готовым, отлаженным и корректно функционирующим прообразом. Более того, используемые в процедурах полезные, эффективные (а в целом ряде случаев и нестандартные) приемы программирования позволяют более глубоко и за более короткий срок освоить программную среду пакета. Использование же во многих процедурах обработки особых и ошибочных ситуаций дает возможность акцентировать уже на ранней стадии внимание на таких важных компонентах создания программных средств, как их надежность, мобильность и ошибкоустойчивость. Наконец, работая с Библиотекой, пользователь не только имеет прекрасную возможность освоить многие из ее средств для своей текущей и последующей работы с пакетом, но и проникается концепцией эффективной организации своих собственных Maple-библиотек, содержащих средства, обеспечивающие его профессиональные интересы и потребности. Есть надежда, что и читатель книги найдет среди средств Библиотеки полезные для своего творчества. В предлагаемой книге рассматриваются принципы работы в программной среде Maple, основу которого составляет язык программирования Maple, что является непосредственным продолжением наших книг упомянутой серии [1-20,22-33,39,41-46,47,49,50,91,103], в которых обсуждаются ПС того же типа, что и рассматриваемое в настоящей книге. Придерживаясь выработанных методики и методологии подготовки указанных книг, наш подход делает основной акцент на изложении материала на основе развернутой апробации описываемой предметной области при решении задач как сугубо теоретических, так и прикладных. В предлагаемой книге представлены базовые сведения по языку программирования Maple – составляющему основу программной среды пакета, в которой пользователь имеет достаточно широкие возможности по разработке собственных Mapleприложений.

Пакет Maple способен решать большое число, прежде всего, математически ориентированных задач вообще без программирования в общепринятом смысле. Вполне можно ограничиться лишь описанием алгоритма решения своей задачи, разбитого на отдельные последовательные этапы, для которых Maple имеет уже готовые решения. При этом, пакет Maple располагает большим набором процедур и функций, непосредственно решающих совсем не тривиальные задачи как то интегрирование, дифференциальные уравнения и др. О многочисленных приложениях Maple в виде т.н. пакетов и говорить не приходится. Тем не менее, это вовсе не означает, что Maple не предполагает программирования. Имея собственный довольно развитый язык программирования (в дальнейшем Maple-язык), пакет позволяет программировать в своей среде самые разнообразные задачи из различных приложений. Несколько поясним данный аспект, которому в отечественной литературе уделяется недостаточно внимания.

Относительно проблематики, рассматриваемой в настоящей книге, вполне уместно сделать несколько существенных замечаний. К большому сожалению, у многих пользователей современных математических пакетов, включая и системы компьютерной алгебры – основной темы нашей книги – бытует достаточно распространенное мнение, что использование подобных средств не требует знания программирования, ибо все, что нужно для решения их задач, якобы уже имеется в этих средствах и задача сводится лишь к выбору нужного средства (процедуры, модуля, функции и т.д.). Подобный подход к данным средствам носит в значительной степени дилетантский характер, причины которого достаточно детально рассмотрены в нашей книге [103].

Двухуровневая лингвистическая поддержка пакета Maple обеспечивается такими языками программирования как С и Maple. В ряде публикаций встречается иная (не вполне обоснованная на наш взгляд) классификацию, когда выделялись три языка – реализации, входной и программирования. Суть же состоит в следующем. Действительно, ядро пакета Maple содержит набор высокоэффективных программ, написанных на С-языке. Более того, библиотека функций доступа к компонентам файловой системы компьютера непосредственно заимствована из соответствующей библиотеки С. По нашим оценкам доля программных средств пакета, написанных на С, не превышает 15%. Остальная масса программных средств пакета (функции, процедуры, модули), находящихся в различных библиотеках, написана на собственном Maple-языке. Уже ввиду сказанного весьма сомнительным выглядит утверждение, что С – язык реализации, а Maple – входной язык или язык программирования. Так как Maple-язык использован для реализации важнейших базовых средств пакета, то языками реализации являются и С, и Maple. При этом, с определенными допущениями можно говорить о входном Maple-языке и языке программирования Maple. В основе своей входной Maple-язык пакета базируется на встроенном языке программирования, являясь его подмножеством, обеспечивающим интерактивный режим работы с пакетом. Именно на входном Maple-языке в этом режиме пишутся и выполняются Maple-документы.

Входной язык ориентирован на решение математически ориентированных задач практически любой сложности в интерактивном режиме. Он обеспечивает диалог пользователя со своей вычислительной компонентой (вычислителем), принимая запросы пользователя на обработку данных с их последующей обработкой и возвратом результатов в символьном, числовом и/или графическом видах. Входной язык является языком интерпретирующего типа и идеологически подобен языкам этого типа. Он располагает большим числом математических и графических функций и процедур, и другими средствами из обширных библиотек пакета. Интерактивный характер языка позволяет легко реализовывать на его основе интуитивный принцип решения своих задач, при котором ход решения можно пошагово верифицировать, получая в конце концов требуемое решение. Уже введя первые предложения в текущий сеанс пакета, вы начинаете работать со входным Maple-языком. В настоящей же книге рассматривается наиболее полная лингвистическая компонента пакета – встроенный Maple-язык программирования (или просто Maple-язык). Вместе с тем, все примеры применения представленных в книге средств являются типичными предложениями входного Maple-языка пакета.

Среда программирования пакета обеспечивается встроенным Maple-языком, являющимся функционально полным процедурным языком программирования четвертого поколения (4GL). Он ориентирован, прежде всего, на эффективную реализацию как системных, так и задач пользователя из различных математически-ориентированных областей, расширение сферы приложений пакета, создание библиотек программных средств и т. д. Синтаксис Maple-языка наследует многие черты таких известных языков программирования как С, FORTRAN, BASIC и Pascal. Поэтому пользователю, в той либо иной мере знакомому как с этими языками, так и с программированием вообще, не составит особого труда освоить и Maple-язык.

Maple-язык имеет вполне традиционные стредства структурирования программ, включает в себя все команды и функции входного языка, ему доступны все специальные операторы и функции пакета. Многие из них являются достаточно серьезными программами, например, алгебраическое дифференцирование и интегрирование, задачи линейной алгебры, графика и анимация сложных трехмерных объектов и т. д. Являясь проблемно-ориентированным языком программирования, Maple-язык характеризуется довольно развитыми средствами для описания задач математического характера, возникающих в различных прикладных областях. В соответствии с языками данного класса структуры управляющей логики и данных Maple-языка в существенной мере отражают характеристику средств, прежде всего, именно для математических приложений. Наследуя многие черты С-языка, на котором написан компилятор интерпретирующего типа, Maple-язык позволяет обеспечивать как численные вычисления с любой точностью, так и символьные вычисления, поддерживая все основные операции традиционной математики. Однако, здесь следует привести одно весьма существенное пояснение.

Хорошо известно, что далеко не все задачи поддаются решению в аналитическом виде и приходится применять численные методы. Несмотря на то, что Maple-язык позволяет решать и такие задачи, его программы будут выполняться медленнее, чем созданные в среде языков компилирующего типа. Так что решение задач, требующих большого объема численных вычислений, в среде Maple может быть весьма неэффективно. Именно поэтому пакет предоставляет как средства перекодировки программ с Maple-языка на C, Java, FORTRAN, MatLab и VisualBasic, так и поддержку достаточно эффективного интерфейса с известным пакетом MatLab.

Средства Maple-языка позволяют пользователю работать в среде пакета в двух режимах: (1) на основе функциональных средств языка с использованием правил оформления и работы с Maple-документом предоставляется возможность на интерактивном уровне формировать и выполнять требуемый алгоритм вашей задачи без сколько-нибудь серьезного знания даже основ программирования, а подобно конструктору собирать из готовых функциональных компонент входного языка на основе его синтаксиса требуемый вам алгоритм, включая его выполнение, отображение результатов на экране (в обычном и/или графическом виде), в файле и в твердой копии, и (2) использовать всю мощь Mapleязыка для создания развитых систем конкретного назначения, так и средств, расширяющих собственно саму среду Maple, чьи возможности определяются только вашими собственными умением и навыками. При этом, первоначальное освоение языка не предполагает предварительного серьезного знакомства с основами программирования, хотя их знание и весьма предпочтительно.

Программирование в среде Maple-языка в большинстве случаев не требует какого-либо особого программистского навыка (хотя его наличие и весьма нелишне), т. к. в отличие от других языков универсального назначения и многих проблемно-ориентированных язык Maple включает большое число математически ориентированных функций и процедур, позволяя только одним вызовом решать достаточно сложные самостоятельные задачи, например: решать системы дифференциальных или алгебраических уравнений, находить минимакс выражения, вычислять производные и интегралы, выводить графики сложных функций и т.д. Интерактивность языка обеспечивает простоту его освоения и удобство редактирования и отладки прикладных Maple-документов и программ. Реальная же мощь Maple-языка обеспечивается не только его управляющими структурами и структурами данных, но и всем богатством его функциональных (встроенных, библиотечных, модульных) и прикладных (Maple-документов) сред-тв, созданных к настоящему времени пользователями из различных прикладных областей, прежде всего, математических. Важнейшим преимуществом Maple является открытость его архитектуры, что позволило в кратчайшие сроки создать широким кругом пользователей из многих областей науки, образования, техники и т.д. обширных наборов процедур и модулей, которые значительно расширили как его возможности, так и сферу приложений. К их числу можно с полным основанием отнести и представленную в [103] Библиотеку, содержащую более 730 средств, дополняющих средства пакета, устраняющих ряд его недоработок, расширяющих ряд его стандартных средств и повышающих уровень совместимости релизов пакета. Представленные в [103] средства используются достаточно широко как при работе с пакетом Maple в интерактивном режиме, так и при программировании различных задач в его среде. Они представляют несомненный интерес при программировании различных задач в среде Maple, как упрощая собственно сам процесс программирования, так и делая его более прозрачным с формальной точки зрения.

Таким образом, пакет Maple – не просто высоко интеллектуальный калькулятор, способный аналитически решать многие задачи, а легко обучаемая система, вклад в обучение которой вносят как сами разработчики пакета, так и его многочисленные пользователи. Очевидно, как бы ни была совершенна система, всегда найдется много специальных задач, которые оказались за пределами интересов ее разработчиков. Освоив относительно простой, но весьма эффективный Maple-язык, пользователь может изменять уже существующие процедуры либо расширять пакет новыми, ориентированными на решение нужных ему задач. Эти процедуры можно включать в одну или несколько пользовательских библиотек, снабдить справочной базой, логически сцепить с главной библиотекой пакета, так что их средства на логическом уровне будут неотличимы от стандартных средств пакета. Именно таким образом и организована наша Библиотека [103,108,109]. И последнее, Maple-язык – наименее подверженная изменениям компонента пакета, поэтому ее освоение позволит вам весьма существенно пролонгировать эффективное использование пакета для решения тех задач, которые прямо не поддерживаются пакетом.

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

В настоящей книге рассматриваются основы Maple-языка программирования в предположении, что читатель в определенной мере имеет представление о работе в Windowsсреде и с самим пакетом в режиме его главного меню (GUI) в пределах, например, книг [8-14,29-33,39,42-46,54-62,103] и подобных им изданий. Представленные ниже сведения по Maple-языку в значительной мере обеспечат вас тем необходимым минимумом знаний, который позволит знакомиться со средствами главной библиотеки пакета и нашей Библиотеки [103]. Данная работа, в свою очередь, даст вам определенный навык программирования, а также обеспечит вас набором полезных приемов и методов программирования (включая и нестандартные) в среде Maple-языка наряду с практически полезными средствами, упрошающими решение многих прикладных задач, составляющих ваши приложения. Кратко охарактеризуем содержание предлагаемой книги.

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

Вторая глава представляет базовые управляющие структуры Maple-языка: ветвления (ifпредложение), организации циклических вычислений (while_do-предложение), а также специальные типы циклических управляющих структур Maple-языка.

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

Четвертая глава рассматривает вопросы организация программных модулей Maple-языка и сохранения процедур и программных модулей в файлах входного и внутреннего форматов.

Пятая глава представляет средства Maple-языка для работы с данными и структурами символьного, строчного, списочного, множественного и табличного типов, в том числе и средства работы с выражениями строчного и символьного типов, множествами, списками и таблицами. Рассматриваются алгебраические правила подстановок для символьных вычислений и др.

Шестая глава представляет средства Maple-языка для обеспечения доступа к внешним файлам данных. Полнота изложения в значительной степени перекрывает как поставляемую с пакетом документацию, так и другие издания. С целью развития системы доступа к файловой системе компьютера нами был разработан целый ряд эффективных средств, представленных как в настоящей главе, так и в нашей Библиотеке [103,108,109].

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

Восьмая заключительная глава знакомит с вопросами создания и ведения библиотек пользователя. Рассмотрены такие вопросы как классический способ создания Maple-библиотек, специальные способы создания библиотек в среде Maple, создание пакетных модулей и др.

Подобно другим ПС, ориентированным на решение задач математического характера, пакет Maple располагает достаточно развитыми средствами, обеспечивающими решение широкого круга задач математического анализа и линейной алгебры. Эти две дисциплины представляют собой основную базовую компоненту современного математического образования естественно-научных специальностей и особых пояснений не требуют. Вместе с тем, следует сразу же отметить, что в дальнейшем вопросы решения задач математического анализа, линейной алгебры и других математически-ориентированных дисциплин в среде Maple (учитывая специфику настоящей книги) не рассматриваются. В принципе, сложности при освоении средств данной группы особой возникать не должно и они достаточно хорошо изложены в многочисленной литературе как зарубежной, так и отечественной [8-14,54-62]. В книгах [41-43,103] мы представили средства, функционирующие в среде пакета релизов 6–10 и ориентированные, прежде всего, на решение задач математического анализа. Там же представлены средства, расширяющие возможности пакета при решении задач линейной алгебры. Данные средства классифицируются относительно стандартных средств двух основных пакетных модулей, ориентированных, прежде всего, на задачи линейной алгебры, а именно: традиционный Maple-модуль linalg и имплантированный модуль LinearAlgebra фирмы NAG. Представлены дополнительные средства, обеспечивающие решение ряда массовых задач линейной алгебры.

Более того, не рассматриваются детально здесь и такие важные средства пакета как графические и обеспечения доступа к файлам данных. Мотивировано данное решение тем, что эти средства довольно хорошо изложены в наших предыдущих книгах, книгах других авторов и в справочной системе по пакету. В частности, в книге [12] достаточно детально рассмотрена система ввода/вывода пакета, которая не претерпела каких-либо существенных изменений вот уже на протяжении 6 релизов. Основной акцент здесь сделан на тех аспектах средств доступа, которые не нашли отражения в имеющихся на сегодня изданиях по Maple-языку, включая и фирменную литературу, стандартно поставляемую с пакетом. В целом же система ввода/вывода пакета может быть охарактеризована следующим образом.

Будучи языком программирования в среде пакета компьютерной алгебры, ориентированного, прежде всего, на задачи алгебраических вычислений, Maple-язык располагает относительно ограниченными возможностями при работе с данными, которые расположены во внешней памяти компьютера. Более того, в этом отношении Maple-язык существенно уступает таким традиционным языкам программирования как C, Cobol, PL/1, FORTRAN, Pascal, Ada, Basic и т. д. В то же самое время Maple-язык, ориентированный, прежде всего, на решение задач математического характера, предоставляет тот набор средств для доступа к файлам данных, которые могут вполне удовлетворить довольно широкую аудиторию пользователей пакета и его физических и математических приложений. В наших книгах [41-42,103] представлен целый ряд дополнительных средств доступа к файлам данных, существенно расширяющих пакет в данном направлении. Многие из них упрощают программирование целого ряда задач, имеющих дело с доступом к файлам данных различной организации, содержания и назначения.

Со всей определенностью можно констатировать, что новые пакетные модули FileTools и LibraryTools были вдохновлены рядом наших книг по Maple-тематике [29-33,39,42-46], с которыми разработчики пакета были ознакомлены. Однако, наш набор подобных процедур является значительно более представительным и они сосредоточены на более широком практическом использовании при решении задач, имеющих дело с обработкой файлов данных. Более того, нам не известно более обстоятельное рассмотрение системы доступа к файлам, обеспечиваемой пакетом, чем в наших предыдущих книгах [8-14, 29-33,39,41-43,45,46,103]. Именно в этом отношении они рекомендуются читателю, имеющему дело с подобными задачами.

В определенной мере вышесказанное можно отнести и к графическим средствам пакета. Так, в вышеуказанных книгах представлены средства, расширяющие стандартный набор графического инструментария пакета Maple релизов 6 – 10. Предлагаемые средства являются довольно полезными процедурами, представляющими определенный прикладной интерес, что подтверждает эффективность их использования при решении целого ряда прикладных задач с использованием графических объектов. В частности, на сегодня, стандартные средства пакета, ориентированные на работу с анимируемыми графическими объектами, имеют целый ряд ограничений на динамическое обновление их характеристик. Между тем, многие задачи, имеющие дело с графическими анимируемыми объектами, предполагают возможность динамического обновления их характеристик. Представлен ряд средств в этом направлении. Заинтересованный читатель отсылается к книгам [8-14,32,78,84,86,88,55,59-62], а также к [91] с адресом сайта (Локальная копия сайта), с которого можно бесплатно загружать некоторые из наших предыдущих книг по данной тематике.

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

Читатель, имеющий опыт программирования в среде Maple, может вполне (быть может, за редким исключением) безболезненно опустить данный материал. Тогда как начинающий на этом поприще получит необходимый минимум сведений по Maple-языку, который позволит значительно проще совершенствоваться в этом направлении. Для более глубокого ознакомления с Maple-языком нами приведены полезные ссылки как на отечественные, так и на зарубежные издания. Более того, читатель, заинтересованный в освоении программной среды пакета Maple, может найти немало полезной информации как в справочной системе по пакету, литературе, поставляемой с пакетом, так и на ведущих Web-серверах и форумах, посвященных Maple-тематике. В век почти массовой компьютеризации и интернетизации лишь ленивый может аппелировать к недостатку нужной информации по какому бы то ни было разделу современного знания.

По ходу настоящей книги будет приведено немало ссылок на наши издания, что следует совершенно непредвзято трактовать и вот почему. Прежде всего, в настояшей книге рассматриваются вопросы программирования в среде пакета Maple, которое является основой разработки в его среде эффективного программного обеспечения, ориентированного на те или иные приложения. В отечественной же литературе других изданий, рассматривающих столь скрупулезно данную проблематику (как, впрочем, и в англоязычной), на мой взгляд, просто нет (и это не только мое субъективное мнение). Наконец, вполне это уместно и в том потребительском контексте, что основная масса цитируемых наших изданий по Maple-тематике может быть совершенно бесплатно получена с указанного в [91] Internet-адреса Гродненского университета. Конечно, там представлены издания, ориентированные на Maple 5 – 7, однако ввиду полной приемственности по основным аспектам программной среды пакета они (с некоторыми оговорками) вполне пригодны и для последующих релизов пакета.

Заключение представляет краткий экскурс в историю создания нашей Maple-библиотеки, которая будет неоднократно цитироваться в настоящей книге. Здесь лишь хочу предупредить, что во многом она создавалась еще в Maple релизов 5–7, когда нас не совсем устраивали как целый ряд базовых средств пакета, так и возможности пакета. Поэтому создавались средства как заполняющие подобный ваккуум, так и устраняющие замеченные недостатки пакета. С появлением новых релизов базовые средства пакета и его средства в целом расширялись и улучшались, поэтому ряд средств библиотеки могут, на первый взгляд, показаться устаревшими (obsolete), однако они все еще представляют интерес с учебной точки зрения, предлагая немало эффективных и полезных приемов программирования, не утративших своей актуальности и поныне. Многие же из библиотечных средств и на сегодня не имеют аналогов. В последние пару лет Maple-тематике в ее прямом смысле мы уделяли относительно немного внимания, используя, между тем, пакет в целом ряде физико-математических и инженерных приложений, а также в преподавании математических курсов для докторантов, различных курсов и мастер-классов по Maple. В будущем данная тематика не планируется в числе наших активных интересов, поэтому наши публикации в этом направлении будут носить в значительной мере спорадический характер. Вот и данное издание, посвященное вопросам сугубо программирования в Maple, также в значительной мере специально не планировалось, поэтому оно не было подвержено тщательному редактированию. Однако смеем надеяться, что и в таком виде оно представит определенный интерес как для начинающих, так и даже имеющих определенный опыт программирования в среде Maple, да и не только.

Еще на одном важном моменте следует акцентировать внимание. Разработка Maple-приложений базируется на программной среде пакета. Здесь можно выделить ряд уровней разработки, которые представляются нам под следующим углом, а именно:

(1) Maple-документы, решающие отдельные прикладные задачи (типичным примером такого подхода может служить набор Maple-документов, решающих типичные инженерно-физические задачи методом конечных элементов [11,13,14,43,110]);

(2) отдельные процедуры, программные модули либо их наборы, сохраненные в файлах и решающие конкретные прикладные задачи (типичными примерами служат средства, созданные многочисленными пользователями пакета, а также пакетные модули);

(3) библиотеки процедур и модулей, организованные аналогично главной библиотеке пакета и решающие достаточно широкий круг как задач из различных приложений, так и системных, расширяющих функциональную среду самого пакета (типичным примером служит наша Библиотека [41,103,108,109]).

Естественно, представленная классификация в значительной степени субъективна, однако при отсутствии иной и она может служить в качестве отправной точки. Средства первого уровня представляют собой законченные Maple-документы, т.е. {mws, mw}-файлы, решающие конкретные прикладные залачи из различных приложений. Они могут содержать описательную часть, постановку задачи, алгоритм, описанный входным Mapleязыком, результаты выполнения документа и другую необходимую информацию. Как правило, сложная задача либо комплекс задач реализуются набором {mws, mw}-файлов, сохраняемых в отдельном каталоге и доступных для выполнения путем загрузки в среду Maple соответствующего релиза либо поочередно, либо в требуемом порядке. Для создания таких документов вполне можно обходиться лишь входным Maple-языком, а не полным набором средств, предоставляемых его расширением – встроенным языком программирования. В целом, данный уровень допускает и диспетчирование документов, например, соедующим образом.

У традиционного Maple-документа после его отладки удаляются все Output-параграфы и он сохраняется в текстовом файле в рамках только своих Input-параграфов. Создается Maple-документ, обеспечивающий загрузку в нужном порядке в текущий сеанс по readпредложению созданные описанным образом txt-файлы, обеспечивая необходимое диспетчирование документов.

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

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

Литература содержит интересные и полезные как зарубежные, так и отечественные источники по Maple-тематике, которые являются вполне доступными. При этом многие из наших изданий (точнее их авторские оригинал-макеты) можно бесплатно получать с указанного в [91] Internet-адреса Гродненского государственного университета им. Я. Купалы. Там же можно найти и массу полезных примеров по Maple-тематике.

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

- ПС – программное средство;

- ПО – программное обеспечение;

- ПК – персональный компьютер;

- альтернативные элементы разделяются символом “|“ и заключаются в фигур- ные скобки, например, {A|B} – A или B;

- смысл конструкции указывается в угловых скобках “< ... >“;

- под выражением “по умолчанию” понимается значение той или иной характе- ристики операционной среды или пакета, которое используется, если пользова- телем не определено для нее иное значение;

- под «регистро-зависимым (независимым)» режимом будем понимать такой режим поиска, сравнения и т.д. символов и строк, когда принимаются (не принимаются) в расчет различия между одинаковыми буквами верхнего и нижнего регистра кла- виатуры. В частности, все идентификаторы в программной среде Maple регист- ро-зависимы;

- понятия “имя УВВ, имя файла, каталог, подкаталог, цепочка каталогов, путь” соот- ветствуют полностью соглашениям MS DOS; при кодировании пути к файлу в

качестве разделителя используется, как правило, сдвоенный обратный слэш (\\);

- СФ - спецификатор файла, определяющий полный путь к нему либо имя;

- СБО (Сlipboard) – системный буфер обмена;

- ГМП (GUI) - главное меню пакета; содержит группы функций его оболочки;

- hhh-файл - файл, имеющий “hhh” в качестве расширения его имени; - под Input-параграфом в дальнейшем понимается вводимая в интерактивном ре- жиме в текущий сеанс информация, слева идентифицируемая символом ввода «>». По умолчанию, ввод оформляется красным цветом. Тогда как под Output- параграфом понимается результат выполнения предшествующего ему Input-па- раграфа (выражения, тексты процедур и модулей, графические объекты и т.д.). Сле- дующий простой пример поясняет вышесказанное:

> A, V:= 2, 12: assign('G' = Int(1/x*sin(x)*cos(x), x)), G = value(G); plot(rhs(%), x = 4 .. V, thickness=A, color=green, axesfont=[TIMES, BOLD, V], labelfont=[TIMES, BOLD, V]);

В дальнейшем ради удобства и компактности там, где это возможно, блок из 2-х параграфов {Input, Output} будем представлять в строчном формате, а именно: > Input-параграф; ⇒ Output-параграф

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

www.aladjev.newmail.ru, www.aladjev.narod.ru, www.aladjev-maple.narod.ru

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

Гродно – Таллинн, февраль, 2007

Глава 1. Базовые сведения по Maple-языку пакета

Пакет Maple способен решать большое число, прежде всего, математически ориентированных задач вообще без программирования в общепринятом смысле. Вполне можно ограничиться лишь описанием алгоритма решения своей задачи, разбитого на отдельные последовательные этапы, для которых Maple имеет уже готовые решения. При этом, Maple располагает довольно большим набором процедур и функций, непосредственно решающих совсем не тривиальные задачи как то интегрирование, дифференциальные уравнения и др. О многочисленных приложениях Maple в виде т.н. пакетов и говорить не приходится. Тем не менее, это вовсе не означает, что Maple не предполагает программирования. Имея собственный достаточно развитый язык программирования (в дальнейшем просто Maple-язык), пакет позволяет программировать в своей среде самые разнообразные задачи из различных приложений. Обсуждение данного аспекта нашло отражение в нашей книге [103], а именно о дилетантском взгляде на вопрос программирования в Maple, да, впрочем, и в ряде других математических пакетов.

В среде пакета можно работать в двух основных режимах: интерактивном и автоматическом (программном). Интерактивный режим аналогичен работе с калькулятором, пусть и весьма высоко интеллектуальным – в Input-параграфе вводится требуемое Maple-выражение и в следующем за ним Output-параграфе получаем результат его вычисления. Так шаг за шагом можно решать в среде пакета достаточно сложные математические задачи, в процессе работы формируя текущий документ (ТД). Для такого режима требуется относительно небольшой объем знаний о самом пакете, а именно:

* знание общей структуры ТД и синтаксиса кодирования выражений;

* знание синтаксиса используемых функциональных конструкций;

* общие правила выполнения, редактирования и сохранения текущего документа.

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

Программирование в среде Maple-языка в большинстве случаев не требует особого программистского навыка (хотя его наличие и весьма нелишне), ибо в отличие от языков универсального назначения и многих проблемно-ориентированных Maple-язык включает большое число математически ориентированных функций, позволяя одним вызовом функции решать достаточно сложные самостоятельные задачи, например: вычислять минимакс выражения, решать системы дифуравнений или алгебраических уравнений, вычислять производные и интегралы, выводить графики сложных функций и др. Тогда как интерактивность языка обеспечивает не только простоту его освоения и удобство редактирования, но также в значительной мере предоставляет возможность эвристического программирования, когда методом «проб и ошибок» пользователь получает возможность запрограммировать свою задачу в полном соответствии со своими нуждами. Реальная же мощь языка обеспечивается не только его управляющими структурами и структурами данных, но и всем богатством его функциональных (встроенных, библиотечных, модульных) и прикладных (Maple-документов) средств, созданных к настоящему времени пользователями из различных прикладных областей, прежде всего физико-математических и технических, а в целом, естественно-научных.

Однако для более сложной работы (выполняющейся, как правило, в программном режиме) требуется знание встроенного Maple-языка программирования, который позволяет использовать всю вычислительную мощь пакета и создавать сложные ПС, не только решающие задачи пользователя, но и расширяющие средства самого пакета. Примеры такого типа представлены как в нашей книге [103], так и в настоящей книге. Именно поэтому, далее представим основные элементы программирования в среде пакета Maple.

Для лингвистического обеспечения решения задач пакет снабжен развитым встроенным проблемно-ориентированным Maple-языком, поддерживающим интерактивный режим. Являясь проблеммно-ориентированным языком программирования, Maple-язык характеризуется довольно развитыми средствами для описания задач математического характера, возникающих в различных прикладных областях. В соответствии с языками этого класса структуры управляющей логики и данных Maple-языка в существенной мере отражают специфику средств именно для математических приложений. Особую компоненту языка составляет его функциональная составляющая, поддерживаемая развитой библиотекой функций, покрывающих большую часть математических приложений. Наследуя многие черты С-языка, на котором был написан компилятор интерпретирующего типа, Maple-язык позволяет обеспечивать как численные вычисления с любой точностью, так и символьную обработку выражений и данных, поддерживая все основные операции традиционной математики [8-14].

Средства Maple-языка позволяют пользователю работать в среде пакета в двух режимах: (1) на основе функциональных средств языка с использованием правил оформления и работы с Maple-документом предоставляется возможность на интерактивном уровне формировать и выполнять требуемый алгоритм прикладной задачи без сколько-нибудь существенного знания даже основ программирования, а подобно конструктору собирать из готовых функциональных компонент языка на основе его синтаксиса требуемый вам алгоритм, включая его выполнение, отображение результатов на экране (в обычном или графическом виде), в файле и в твердой копии, и (2) использовать всю мощь языка как для создания развитых систем конкретного назначения, так и средств, расширяющих собственно саму среду пакета, возможности которых определяются только вашими собственными умением и навыками. При этом, первоначальное освоение языка не предполагает предварительного серьезного знакомства с основами программирования, хотя знание их и весьма предпочтительно. Тут же уместно отметить, что значительная часть функциональных средств самого пакета написана и скомпилирована именно на Maple-языке, позволившим создать довольно эффективные относительно основных ресурсов ЭВМ загрузочные модули. Анализ данных средств является весьма неплохим подспорьем при серьезном освоении Maple-языка. Наряду с этим, в архиве [109] представлены исходные тексты процедур и программных модулей нашей Библиотеки [41], которые также могут служить в качестве неплохого иллюстративного материала для осваивающих программную среду пакета.

В настоящей главе рассматриваются базовые элементы Maple-языка в предположении, что читатель в определенной мере имеет представление о работе в Windows-среде и с самим пакетом в режиме его графического меню (GUI) в пределах главы 3 [12] либо, например, книг [29-33,45,46,54-62,78-89,103,108,111-130] и подобных им изданий.

1.1. Базовые элементы Maple-языка пакета

Определение Maple-языка можно условно разбить на четыре базовые составляющие, а именно: алфавит, лексемы, синтаксис и семантику. Именно две последние составляющие и определяют суть того или иного языка. Синтаксис составляют правила образования корректных предложений из слов языка, которые должны строго соблюдаться. В случае обнаружения на входе Maple-предложения с синтаксической ошибкой выводится диагностическое сообщение, сопровождаемое “^”-указателем на место возможной ошибки или установкой в место ошибки |-курсора, как это иллюстрирует следующий фрагмент:

> A:= sqrt(x^2+y^2+z^2); V:=x*56 |Z:= sin(x)/(a+b); Error, missing operator or `;`

> A:= sqrt(x^2+y^2+z^2): V:= x**|*56: Z:= sin(x)/(a+b); Error, `*` unexpected

> read `C:\\ARM_Book\\Grodno\\Academy.99`: on line 0, syntax error, `*` unexpected:

VGS:=56***sin(x)/sqrt(Art^2+Kr^2)-98*F(x,y);

^

Error, while reading `C:\ARM_Book\Grodno\Academy.99`

При этом следует иметь в виду два обстоятельства: (1) идентифицируется только первая встреченная ошибка и (2) при наличии синтаксических ошибок (например, несогласования открывающих и закрывающих скобок) в сложных выражениях язык может затрудняться с точной их диагностикой, идентифицируя ошибочную ситуацию сообщением вида “ `;` unexpected”, носящим в целом ряде случаев весьма приблизительный характер. Mapleязык производит синтаксический контроль не только на входе конструкций в интерактивном режиме работы, но и в момент считывания их из файлов. В последнем случае синтаксическая ошибка инициирует вывод соответствующего диагностического сообщения указанного выше вида с указанием номера первой считанной строки, содержащей ошибку, и идентификацией спецификатора файла, как это иллюстрирует последний пример фрагмента. Ниже вопросы синтаксического анализа Maple-конструкций будут рассмотрены более детально, прежде всего, при рассмотрении parse-функции пакета.

В отличие от синтаксиса, определяющего правила составления корректных языковых конструкций, семантика языка определяет алгоритмы их обработки, т.е. определяет их понятийное назначение с точки зрения самого языка. Например, результатом обработки конструкции вида “W:= 57*sin(19.99);” является присвоение W-переменной результата произведения целого числа “57” и значения sin-функции в точке “19.99” ее вызова. При этом, определяется как собственно результат, так и его тип. В связи со сказанным наряду с синтаксическими, как правило, распознаваемыми языком, могут возникать и семантические ошибки, которые язык не распознает, если они при этом, не инициируют, в свою очередь, ошибок выполнения. Типичным примером семантических ошибок является конструкция вида “A/B*C”, трактуемая языком как “(A*C)/B”, а не как “A/(B*C)” на первый взгляд. Как правило, семантические ошибки выявляются на стадии выполнения Maple-программы или вычисления отдельных Maple-выражений и данная процедура относится к этапу отладки программ и процедур, рассматриваемому несколько ниже.

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

♦ заглавные и прописные буквы латинского алфавита (A .. ÷Z; a .. ÷z; 52);

♦ десятичные цифры (0 .. 9; 10);

♦ специальные символы (` ! @ # $ % ^ & * ( ) _ + { } : “ < > ? | - = [ ] ; ‘ , . / \; 32); ♦ заглавные и прописные буквы русского алфавита (кириллицы: A .. Я; a .. я; 64).

Синтаксис Maple-языка объединяет символы входного алфавита в лексемы, состоящие из ключевых (зарезервированных) слов, операторов, строк, натуральных чисел и знаков пунктуации. Рассмотрим несколько детальнее каждую из составляющих. В качестве ключевых Maple-язык использует слова, представленные в следующей табл. 1.

Таблица 1

Ключевые слова:

Смысловая нагрузка:

if, then, else, elif, fi

условное if-предложение языка

for, from, in, by, to, while, do, od

предложения циклических конструкций

proc, local, global, option, description, end

процедурные выражения языка

read, save

функции чтения и записи выражений

done, quit, stop

функции завершения работы

union, minus, intersect

операторы над множествами

and, or, not

логические операторы языка

mod

оператор вычисления по модулю

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

> local:= 64;

Error, reserved word `local` unexpected

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

Операторы языка относятся к трем типам: бинарные, унарные и нульарные. Допускаемые языком унарные операторы представлены в следующей табл. 2.

Таблица 2

Унарный оператор:

Смысловая нагрузка оператора:

+

префиксный плюс

-

префиксный минус

{!|factorial}

факториал (постфиксный оператор)

$

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

.

десятичная точка (префиксный или постфиксный)

%<целое>

оператор метки

not

префиксный логический оператор отрицания

&<строка>

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

Унарные операторы (+, -, not) и десятичная точка (.) имеют вполне прозрачный смысл и особых пояснений не требуют. Здесь мы кратко остановимся на операторе метки (%); при этом понятие метки в Maple-языке существенно иное, чем для традиционных языков программирования. Унарный оператор метки кодируется в виде %<целое> и служит для представления общих подвыражений большого выражения в целях более наглядного представления второго. Данный оператор используется Maple-языком для вывода выражений на экран и на принтер в удобном виде. После возвращения языком представленного в терминах %-оператора выражения к переменным %<целое> можно обращаться как к обычным определенным переменным. Для возможности использования представления выражений в терминах %-оператора используются две опции interface-переменной оболочки пакета: labelling и labelwidth, определяющих соответственно допустимость такого представления и длину меченных подвыражений. При этом, данная возможность допустима не для всех форматов Output-параграфа текущего документа.

Допустимые языком базовые бинарные операторы представлены в следующей табл. 3.

Таблица 3

Оператор

Смысловая нагрузка:

Оператор

Смысловая нагрузка:

+

сложения

<

меньше чем

-

вычитания

<=

меньше чем или равно

*

умножения

>

больше чем

/

деления

>=

больше чем или равно

{**|^}

степени

=

равно

:=

присваивания

<>

не равно

:-

выбора элемента модуля

,

разделитель выражений

$

последовательности

->

функциональный

@

композиции функций

mod

взятие модуля

@@

кратной композиции

union

объединение множеств

::

определения типа

minus

разность множеств

&<строка>

нейтральный инфексный

intersect

пересечение множеств

||

конкатенации строк

and

логическое И

..

ранжирования

or

логическое ИЛИ

С большинством из приведенных в табл. 3 унарных операторов читатель должен быть хорошо знаком, тогда как такие как (:=, $, @, @@, ..) в определенной мере специфичны для математически ориентированных языков и будут рассмотрены ниже. По остальным же пояснения будут даваться ниже по мере такой необходимости.

Наконец, три нульарных оператора %, %% и %%% (один, два и три знака процентов) представляют специальные идентификаторы Maple-языка, принимающие в качестве значений результат вычисления соответственно последнего, предпоследнего и предпредпоследнего предложения. Следующий фрагмент иллюстрирует применение нульарных операторов:

> AG:= 59: AV:= 64: AS:= 39: (%%+3*%+%%%-2); ⇒ 238

> P:=proc() args; nargs; %, %% end proc: P(59, 64, 39, 17, 10, 44); ⇒ 6, 59, 64, 39, 17, 10, 44

Как правило, нульарные операторы используются в интерактивном режиме работы с пакетом и выступают на уровне обычных переменных, позволяя обращаться к результатам предыдущих вычислений на глубину до трех. Однако использование их вполне допустимо и в теле процедур, где они выступают на уровне локальных переменных, как это иллюстрирует второй пример фрагмента. Вопросы организации процедур рассматриваются ниже. Наряду с возможностью использования {%|%%|%%%}-конструкции для доступа к результатам предыдущих вычислений в текущем сеансе работы языком предоставляется history-механизм обращения к любому полученному в рамках его истории вычислению. Детально данный механизм рассмотрен в нашей книге [12]. В частности, там же представлен и анализ данного механизма, говорящий о недостаточно продуманной и реализованной системе, обеспечива-ющей history-механизм; в целом весьма полезного средства, но при наличии существенно более широких возможностей, подобных, например, пакету Mathematica [6,7], где подобный механизм представляется нам намного более развитым.

Приоритетность операторов Maple-языка в порядке убывания определяется как:

|| :- :: % & ! {^, @@} { ., *, &*, /, @, intersect} {+, -, union, minus} mod subset

.. {<, <=, >, >=, =, <>, in} $ not and or xor implies -> , assuming :=

В качестве строк Maple-язык рассматривает любые последовательности символов, кодируемые в верхних двойных кавычках ("), например: "Академия Ноосферы". При этом, составлять строку могут любые допустимые синтаксисом языка символы. При необходимости поместить в строку верхнюю двойную кавычку ее следует дублировать либо вводить комбинацией вида (\"). Это же относится и к ряду других символов, вводимых посредством обратного слэша (\), как это иллюстрирует следующий весьма простой фрагмент:

> `Строка`:= "Международная Академия Ноосферы"; Строка:= "Международная Академия Ноосферы"

> `Строка 1`:= "Международная\nАкадемия\nНоосферы";

Строка 1 := "Международная

Академия

Ноосферы"

> `Строка 2`:= "Internatio\nal Academy of Noosphere"; Строка 2 := "Internatio nal Academy of Noosphere"

> `Строка 3`:= "Российская Эколо"гическая Академия";

Error, missing operator or `;`

Однако, как иллюстрируют примеры фрагмента, если наличие в строке одинарной двойной кавычки (помимо ограничивающих) вызывает синтаксическую ошибку, то для случая обратного слэша в общем случае могут возникать семантические ошибки. Максимальная длина строки зависит от используемой платформы: для 32-битных ЭВМ - 524271 символов, а для 64-битных - 34359738335 символов. О средствах работы со строчными структурами детальнее речь будет идти несколько ниже.

В качестве символов Maple-язык рассматривает любые последовательности символов, в общем кодируемые в верхних обратных кавычках (`), например: `Академия Ноосферы 64`. При этом, составлять символ могут любые допустимые синтаксисом языка символы. При необходимости поместить в символ верхнюю обратную кавычку ее следует дублировать либо вводить комбинацией вида (\`). Это же относится и к ряду других символов, вводимых посредством обратного слэша (\), как это иллюстрирует фрагмент:

> `Строка`:= `Международная Академия Ноосферы`; Строка:= Международная Академия Ноосферы

> `Строка 1`:= `Международная\nАкадемия\nНоосферы`;

Строка 1 := Международная

Академия Ноосферы

> `Строка 2`:= `Internatio\nal Academy of Noosphere`; Строка 2 := Internatio nal Academy of Noosphere

> `Строка 3`:= `Российская Эколо`гическая Академия`; Error, missing operator or `;`

Сразу же следует отметить одно принципиальное отличие строк от символов Maple-языка. Если символам можно присваивать значения, то строки такой процедуры не допус- кают, вызывая ошибочные ситуации, например:

> "Данные":= 2006;

Error, invalid left hand side of assignment

> Данные:= 2006: `Результат`:=350: Данные, Результат;

2006, 350

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

В качестве натурального целого язык рассматривает любую последовательность из одной или более десятичных цифр, при этом все ведущие нули игнорируются, т.е. 005742 рассматривается как 5742. Длина таких чисел зависит от используемой пакетом платформы и на большинстве 32-битных ЭВМ составляет порядка 524280 десятичных цифр, тогда как на 64-битных она достигает уже 38654705646 цифры. В качестве целого рассматривается натуральное целое со знаком или без. Для работы с арифметикой целых чисел язык располагает достаточно развитыми средствами, рассматриваемыми ниже.

Наконец, знаки пунктуации Maple-языка представлены в следующей табл. 4.

Таблица 4

Знак пунктуации:

Смысловая нагрузка:

; и :

точка с запятой и двоеточие

( и )

левая и правая круглые скобки

< и >

левая и правая угловые скобки

{ и }

левая и правая фигурные скобки

[ и ]

левая и правая квадратные скобки

"

двойная верхняя кавычка

|

вертикальный разделитель

` ' , .

кавычка, апостроф, запятая и точка

Представим использование Maple-языком указанных в табл. 4 знаков пунктуации:

: и ; - служат для идентификации конца предложений Maple-языка; различия между ними обсуждаются ниже;

( ) - для группировки термов в выражениях, а также формальных или фактических аргументов при определениях или вызовах функций/процедур;

<> - для определенной пользователем группировки выражений;

{} - для определения структур данных типа множество;

[] - для определения структур данных типа список, а также для образования индек- сированных переменных и оператора выбора элемента из индексированных вы- ражений;

` ' , - соответственно для определения идентификаторов, невычисляемых выражений и структур типа последовательность; при этом, следует четко различать при коди- ровании конструкций Maple-языка символы верхней обратной кавычки (96) и апо- строфа (39), в скобках даны их десятичные коды по внутренней кодовой таблице; " - верхние двойные кавычки служат для определения строчных структур данных.

Для разделения лексемов синтаксис языка допускает использование как знаков пунктуации, так и пробелов, понимаемых в расширенном смысле, т.е. в качестве пробелов допускается ис-ользование символов: собственно пробела (space), табуляции (tab), перевода строки и возврата каретки. При этом, сами символы пробела не могут входить в состав лексемов, являясь их разделителями. Между тем, space-символ может входить в состав строк и символов, образованных двойными или одинарными верхними кавычками. Использование же его в составе лексема, как правило, инициирует ошибочную ситуацию, например: > AGN: = sqrt(x^2 + y^2 + z^2 + 19.47);

Error, `=` unexpected

Под программной строкой в Maple-языке понимается строка символов, завершающаяся символами перевода строки и возврата каретки (16-ричные коды 0D0A); при этом, сами эти символы строке не принадлежат. Пустые программные строки образуются простым нажатием Enter-клавиши. Завершение программной строки по Enter-клавише в интерактивном режиме вызывает немедленное вычисление всех содержащихся в ней выражений и предложений языка. Если в программной строке содержится #-символ, то язык рассматривает всю последующую за ним информацию в качестве комментария и обработки ее не производит. Этот прием можно использовать для комментирования текстов Mapleпрограмм, тогда как в интерактивном режиме особого смысла он не имеет. Вывод длинных строк производится в несколько строк экрана, идентифицируя символом обратного слэша (\) продолжение строки. При этом символ обратного слэша несет более широкую смысловую нагрузку, а именно: наряду с функцией продолжения он может выступать в качестве пассивного оператора и средства ввода управляющих символов.

В первом качестве он используется, как правило, для разбиения на группы строчных структур или цифровых последовательностей в целях более удобного их восприятия. Тогда как во втором случае он позволяет вводить управляющие символы, производящие те или иные действия. В этом случае кодируется конструкция следующего вида: ”\<управляющий символ>”, где управляющий символ является одним из следующих восьми {a, b, e, f, n, r, t, v}; например, по конструкции “\n” производится перевод строки, а по “\a” – звонок, с другими детально можно ознакомиться по книге [10] либо по Help-системе пакета посредством предложения вида: > ?backslash. Следующий фрагмент иллюстрирует использование символа обратного слэша в указанных выше контекстах:

> A:= `aaaaaaaaaaa\nbbbbbbbbbbbb\nccccccccccccc`; # Перевод строки A := aaaaaaaaaaa bbbbbbbbbbbb ccccccccccccc

> A:= `aaaaaaaaaa\a\nbbbbbbbbbbb\a\ncccccccccccc`; # Звонки с переводом строки A := aaaaaaaaaa• bbbbbbbbbbb• cccccccccccc

> 1942.1949\1959\1962\1966\1972\1995\1999\2006;

1942.19491959196219661972199519992006

Употребление обратного слэша без следующего за ним одного из указанных управляющих символов в середине лексема приводит к его игнорированию, т.е. он выступает в качестве пустого оператора. Для действительного ввода в строку обратного слэша его нужно кодировать сдвоенным. При этом, следует иметь в виду, что в случае кодирования спецификаторов файлов в функциях доступа в качестве разделителей подкаталогов можно использовать сдвоенные обратные слэши (\\) или одинарные прямые слэши (/), в противном случае возможно возникновение семантических ошибок. По конструкции вида kernelopts(dirsep) возвращается стандартный разделитель подкаталогов, принимаемый по умолчанию, однако его переопределение невозможно.

После успешной загрузки пакета Maple производится выход на его главное окно, структура и назначение компонент которого детально рассмотрены, например, в [9-14]. В области текущего документа в первой строке устанавливается >-метка ввода, идентифицирующая запрос на ввод информации, как правило, завершающийся {;|:}-разделителем с последующим нажатием Enter-клавиши либо активацией !-кнопки 4-й строки главного окна. Результатом данной процедуры является передача пакету программной строки, содержащей отдельное Maple-выражение, вызов функции либо несколько предложений языка. Под Maple-программой (документом) понимается последовательность предложений языка, описывающая алгоритм решаемой задачи, и выполняемая, если не указано противного, в естественном порядке следования своих предложений.

В интерактивном режиме ввод программной строки производится непосредственно за >-меткой ввода (Input-параграф текущего документа) и завершается по клавише Enter, инициирующей синтаксический контроль введенной информации с последующим вычислением (если не обнаружено синтаксических ошибок) всех входящих в строку Maple-предложений. В дальнейшем мы ради удобства представления иллюстративных примеров программную строку и результат ее вычисления будем иногда представлять в следующем достаточно естественном формате:

> Maple-предложение {:|;} ⇒ Результат вычисления

При этом, в зависимости от завершения Maple-предложения {;|:}-разделителем результат его вычисления соответственно {выводится|не выводится} в текущий документ, т. е. {формируется|не формируется} Output-параграф. Однако следует иметь в виду, что «не формируется» вовсе не означает «не возвращается» – результат выполнения Input-параграфа всегда возвращается и его можно получать, например, по нульарному %-оператору, как это иллюстрирует следующий весьма простой фрагмент:

> Tallinn:= 2006; ⇒ Tallinn := 2006

> %; ⇒ 2006

> Tallinn:= Grodno: %; ⇒ Grodno

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

> x:= 5: assign('y' = 64); assign('z' = 59); assign('s' = 39); %; ⇒ 5

Использование (:)-разделителя служит, главным образом, для того, чтобы избавиться в Maple-документе от ненужного вывода промежуточной и несущественной информации. Структурная организация Maple-документов достаточно детально была рассмотрена в [9-14,78-89]. Здесь мы лишь отметим особые типы программных строк, начинающихся с двух специальных управляющих {?, !}-символов Maple-языка пакета.

Использование в самом начале программной строки в качестве первого, отличного от пробела, ?-символа рассматривается Maple-языком как инструкция о том, что следующая за ним информация - фактические аргументы (последовательность которых разделяется запятой “,” или прямым слэшем “/”) для help-процедуры, обеспечивающей вывод справочной информации по указанным аргументами средствам пакета. Например, по конструкции формата “> ?integer“ выводится справка integer-раздела Help-системы пакета.

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

1.2. Идентификаторы, предложения присвоения и выделения Maple-языка

Основной базовой единицей языка является предложение, представляющее собой любое допустимое Maple-выражение, завершающееся {;|:}-разделителем. Более того, не нарушая синтаксиса Maple-языка, под предложением будем понимать любую допустимую Maple-конструкцию, завершающуюся {;|:}-разделителем; при этом, предложение может завершаться и по Enter-клавише (символы перевод строки и возврат каретки), если оно содержит единственное выражение. В ряде случаев допустимо использование в качестве разделителя Maple-предложений даже запятой, если они находятся внутри программной строки, что позволяет выводить результаты вычислений в строчном (разделенном запятой) формате. Однако это требует весьма внимательного подхода, как к нетипичному приему. Следующий фрагмент иллюстрирует все перечисленные способы кодирования Maple-предложений:

> V:= 64: G:= 59: S:= 39: Art:= 17: Kr:= 10: V ,G, S, Art, Kr;

64, 59, 39, 17, 10

> ?HelpGuide # вывод справки по HelpGuide

> R := evalf( Art2 + Kr2 + V2 + G2 + S2 , 6) Z := Art 2 + Kr 2 + V 2 + G 2 + S 2

R := 97.4012 Z := 9487

if 17 ≤ evalf( Art2 + Kr2 ) then evalf  Art 89  else evalf  96 Kr  end if

> 

5.235294118

> V:= 64: G:= 59: Art:= 17: Kr:= 10: V, G, S, Art, H:= 2006: Kr; Error, cannot split rhs for multiple assignment

10

> assign('V', 42), assign('G', 47), assign('Art', 89), assign('Kr', 96), V, G, Art; 42, 47, 89

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

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

Идентификаторы . В терминологии пакета под символами (symbol) понимаются как собственно цепочки символов, так и идентификаторы, удовлетворяющие соглашениям пакета, т.е. имена для всех конструкций Maple-языка пакета. Идентификаторы служат для установления связей между различными компонентами вычислительного процесса как логических, так и информационных, а также для образования выражений и других вычислительных конструкций. В качестве идентификатора в Maple-языке выступает цепочка из не более, чем 524271 символов для 32-битной платформы и не более 34359738335 символов для 64-битной платформы, начинающаяся с буквы либо символа подчеркивания (_). Идентификаторы регистро-зависимы, т.е. одинаковые буквы на верхнем и нижнем регистрах клавиатуры полагаются различными. Данное обстоятельство может служить, на первых порах, источником синтаксических и семантических ошибок, ибо в большинстве современных ПС идентификаторы, как правило, регистро-независимы. В качестве примеров простых идентификаторов можно привести следующие:

AVZ, Agn, Vs_A_K, Ar_10, KrV, Tall_Est, Salcombe_Eesti_Ltd_99, Vasco_97

Для возможности определения русскоязычных идентификаторов либо идентификаторов, содержащих специальные символы, включая пробелы, их следует кодировать в верхних кавычках, как это иллюстрирует следующий простой пример:

> `ТРГ=TRG_9`:= 64: `Значение 1`:= 1942: evalf(`Значение 1`/`ТРГ=TRG_9`, 12);

30.3437500000

> Таллинн:= 56: Гродно:= 84: Вильнюс:= 100: Таллинн + Гродно + Вильнюс;

240

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

Наряду с такими простыми конструкциями идентификаторов Maple-язык пакета допускает и более сложные, определяемые несколькими путями. Прежде всего, идентификаторы, начинающиеся с комбинации символов (_Env) полагаются ядром сеансовыми, т. е. их действие распространяется на весь текущий сеанс работы с пакетом. Так как они служат, прежде всего, для изменения пакетных предопределенных переменных, то их использованию следует уделять особое внимание. По конструкции anames('environment') можно получать в текущем сеансе все активные пакетные переменные: > anames('environment');

Testzero, UseHardwareFloats, Rounding, %, %%%, Digits, index/newtable, mod, %%, Order, printlevel, Normalizer, NumericEventHandlers

Ниже данный тип переменных будет нами рассматриваться несколько детальнее. Для обозначения определенного типа числовых значений Maple-язык использует целый ряд специальных идентификаторов таких, как: _N, _NN, _NNp, _Z∼, _Zp∼, _NN∼ и др. Например, _Zp∼ и _NN∼ используются для обозначения целых и целых неотрицательных чисел, тогда как _Cp-переменные используются для обозначения постоянных интегрирования и т.д.

Пакетные переменные такие как Digits, Order, printlevel имеют приписанные им по умолчанию значения соответственно [10, 6, 1], которые можно переопределять в любой момент времени. Однако, если после выполнения restart-предложения, восстанавливающего исходное состояние ядра пакета, сеансовые переменные становятся неопределенными, то пакетные переменные восстанавливают свои значения по умолчанию. Более сложные виды идентификаторов, включающие целые фразы как на английском, так и на национальных языках, можно определять, кодируя их в верхних обратных кавычках; при этом, внутри ограничивающих кавычек могут кодироваться любые символы (при использовании верхних кавычек они дублируются). В случае простого R-идентификатора конструкции R и `R` являются эквивалентными, а при использовании ключевых слов Maple-языка (указанных в 1.1) в качестве идентификаторов они должны кодироваться в верхних обратных кавычках.

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

> ``:= 64: ``; ⇒ 64

однако по целому ряду соображений этого делать не следует, то же относится и к символам ` `, ` `, ` ` и т.д. В противном случае могут возникать ошибочные и непредсказуемые ситуации. Вместе с тем, пустой символ (как и строка) отличен от значения глобальной NULL-переменной пакета, определяющей отсутствие выражение, т.е. ничего.

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

Имя(Последовательность фактических аргументов)

где передаваемые ей фактические аргументы определяют исходные данные для выполнения алгоритма функции и возвращаемый ею результат. Детально вопросы организации и механизма функциональных средств Maple-языка рассматриваются ниже.

Вторым способом определения сложных идентификаторов является кодирование объединяющих их составные части ||-оператора конкатенации и/или встроенной cat-функции конкатенации строк, имеющей простой формат кодирования cat(x, y, z, t, ...) и возвращающей объединенную строку (символ) xyzt... , где: x, y, z, t … - строки, символы или

||-операторы (при этом, ||-оператор не может быть первым или последним), как это иллюстрирует следующий весьма простой фрагмент:

> cat(111, aaa||bbb, ccc), cat("111", aaa||bbb, ccc||ddd), `111`||cat(aaa, bbb)||"222";

111aaabbbccc, "111aaabbbcccddd", (111cat(aaa, bbb)) || "222"

> `11111`||22222, "11111"||22222, cat(`11111`, 22222), cat("11111", 22222);

1111122222, "1111122222", 1111122222, "1111122222"

> 11111||22222; Error, `||` unexpected

> cat(11111, 22222); ⇒ 1111122222

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

cat(x1, x2, ..., xj, ..., xn) = x1||x2|| ... ||xj|| ... xn

Результат конкатенации должен использоваться в кавычках, если составляющие его компоненты включали специальные символы. Полученные в результате конкатенации символы используются в дальнейшем как единые идентификаторы или просто символьные (строчные) значения. В принципе, допуская конкатенацию произвольных Maple-выражений, функция cat и ||-оператор имеют ряд ограничений, в частности, при использовании их в качестве аргументов и операндов функциональных конструкций, которые могут инициировать некорректные (а в ряде случаев и непредсказуемые) результаты:

> cat(ln(x), sin(x), tan(x)); ⇒ || (ln(x)) || (sin(x)) || (tan(x))

> ` `||F(x)||G(x), " "||F(x)||G(x); ⇒ ( F(x)) || G(x), (" F"(x)) || G(x)

> cat(F(x), G(x)), H||evalf(Pi); ⇒ || (F(x)) || (G(x)), Hevalf(Pi)

> H||(evalf(Pi)); ⇒ H || (3.141592654)

> cat(`sin(x)`, "cos(x)"); ⇒ sin(x)cos(x)

> [A||max(59, 64), A||(max(59, 64))]; ⇒ [Amax(59, 64), A64]

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

Идентификаторы ключевых слов Maple-языка кодируются в верхних кавычках при использовании их в качестве аргументов функций либо обычных переменных, однако по целому ряду соображений последнего делать не рекомендуется. Не взирая на возможность использования в качестве идентификаторов произвольных символов, использовать ее рекомендуется только в случае необходимости контекстного характера, ибо в противном случае выражения с ними становятся мало обозримыми. В качестве идентификаторов допускается использование и русских имен и выражений, однако их следует как при определении, так и при использовании кодировать в верхних обратных кавычкам.

Наряду с рассмотренными типами Maple-язык допускает использование индексированных идентификаторов, имеющих следующий формат кодирования:

Идентификатор[Последовательность индексов]

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

> A:= 64*V[1, 1] + 59*V[1, 2] - 10*V[2, 1] - 17*V[2, 2];

A := 64 V1, 1 + 59 V1, 2 − 10 V2, 1 − 17 V2, 2

> AG[47][59]:= 10*Kr[k][96] + 17*Art[j][89];

AG47 := 10 Krk + 17 Artj

59 96 89

Однако, при условии V-массива V[k, h]-переменная идентифицирует его (k, h)-элемент. И так как индексированность сохраняет свойство быть идентификатором, то его можно последовательно индексировать, как это иллюстрирует последний пример фрагмента.

На основе индексированной переменной базируется и предложение выделения, кодируемое подобно первой в следующем простом формате:

Идентификатор[Последовательность индексов] {;|:}

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

> L:= [59, 64, 39, 10, 17]: S:={V, G, Sv, Art, Kr}: L[3], S[5], S[3], L[4];

39, Sv, Art, 10

Наконец, идентификатор функции/процедуры кодируется в следующем формате: Идентификатор(Последовательность фактических аргументов)

определяя вызов функции с заданным именем с передачей ей заданных фактических аргументов, например: sin(20.06); ⇒ 0.9357726776.

Для идентификаторов любых конструкций Maple-языка допускается использование алиасов (дополнительных имен), позволяющих обращаться к конструкциям как по их основным именам, так и по дополнительным. Данный механизм языка обеспечивается встроенной alias-функцией, кодируемой в следующем формате: alias(alias_1=Id, alias_2=Id, ... , alias_n=Id)

где Id - основное имя, а alias_j - присваиваемые ему алиасы (j=1..n), например:

> G:= `AVZ`: alias(year = 'G', Grodno = 'G', `ГрГУ` = 'G'); ⇒ year, Grodno, ГрГУ

> [G, year, Grodno, `ГрГУ`]; ⇒ [AVZ, AVZ, AVZ, AVZ]

> alias(e = exp(1)); ⇒ year, Grodno, ГрГУ, e

> evalf(e); ⇒ 2.718281828

В приведенном фрагменте проиллюстрировано, в частности, определение более привычного e-алиаса для основания натурального логарифма, вместо принятого (на наш взгляд не совсем удачного) в пакете exp(1)-обозначения. Алиасы не допускаются только для числовых констант; при этом, в качестве Id-параметров alias-функции должен использоваться невычисленный идентификатор (т.е. кодируемый в апострофах), а не его значение. Если вызов alias-функции завершается (;)-разделителем, то возвращается последовательность всех на текущий момент присвоенных алиасов сеанса работы с ядром пакета. Механизм алиасов имеет немало интересных приложений, детальнее рассматриваемых в наших книгах [10-12,91].

Близкой по назначению к alias-функции является и функция macro формата: macro(X1 = Y1, ...., Xn = Yn)

возвращающая NULL-значение, т.е. ничего, и устанавливающая на период сеанса работы с ядром пакета односторонние соотношения X1 ⇒ Y1, ..., Xn ⇒ Yn. Точнее, любое вхождение Xj-конструкции в Input-параграфе либо при чтении ее из файла замещается на приписанную ей по macro-функции Yj-конструкцию. Исключением является вхождение Xj-конструкций в качестве формальных аргументов и локальных переменных процедур. В данном отношении macro-функция отличается от традиционного понятия макроса и более соответствует однонаправленному алиасу. Функция macro может быть определена для любой Maple-конструкции, исключая числовые константы. Более того, фактические аргументы macro-функции не вычисляются и не обрабатываются другими macro-функциями, не допуская рекурсивных macro-определений. Для изменения определения macro вполне достаточно выполнить новый вызов macro-функции, в которой правые части уравнений имеют другое содержимое. Тогда как для отмены macro-определения достаточно произвести соответствующий вызов функции macro(Xp = Yp). Следующий простой фрагмент иллюстрирует вышесказанное:

> restart; macro(x = x*sin(x), y = a*b+c, z = 56):

> HS:= proc(x) local y; y:= 42: y*x^3 end proc:

> macro(HS=AGN, x = x, y = 47, z = 99): map(HS, [x, y, z]);

[AGN(x), AGN(47), AGN(99)] > HS:= proc(x) local y; y:= 42: y*10^3 end proc:

> map(HS, [x, y, z]); ⇒ [42000, 42000, 42000]

> macro(HS=AGN, x = x, y = 47, z = 99): map(HS, [x, y, z]);

[AGN(x), AGN(47), AGN(99)]

> restart: HS:= proc(x) local y; y:= 42: y*x^3 end proc: HS(1999); 335496251958

> macro(HS=AGN): [AGN(1999), HS(1999)];

[AGN(1999), AGN(1999)]

> alias(AGN=HS): [AGN(1999), HS(1999)];

Warning, alias or macro HS defined in terms of AGN

[335496251958, AGN(1999)]

Из примеров фрагмента, в частности, следует вывод о необходимости достаточно внимательного использования macro-функции для идентификаторов процедур, ибо их переопределение приводит к неопределенности нового идентификатора со всеми отсюда вытекающими последствиями. При этом, в целом, ситуацию не исправляет и последующее использование alias-функции. На это следует обратить особое внимание.

Определение переменной в текущем документе (ТД) является глобальным, т.е. доступным любому другому ТД в течение текущего сеанса работы с ядром пакета. Сказанное не относится к режиму параллельного сервера, когда все загруженные документы являются независимыми. Режим параллельного сервера детально рассмотрен в [9-12]. При этом, определение считается сделанным только после его реального вычисления. После перезагрузки пакета все определения переменных и пользовательских функций/процедур (отсутствующих в библиотеках, логически сцепленных с главной библиотекой пакета) теряются, требуя нового переопределения. Без перезагрузки пакета этого можно добиться по предложению restart, приводящему все установки ядра пакета в исходное состояние (очистка РОП, отмена всех сделанных ранее определений, выгрузка всех загруженных модулей и т.д.), либо присвоением идентификаторам переменных невычисленных значений вида Id:='Id'. Следующий простой фрагмент иллюстрирует вышесказанное:

> x:= 19.42: y:= 30.175: Grodno:= sin(x) + cos(y); ⇒ Grodno := 0.8639257079

> restart; Grodno:= sin(x) + cos(y); ⇒ Grodno := sin(x) + cos(y)

> G:= proc() nargs end proc: G(42, 47, 67, 62, 89, 96); ⇒ 6

> G:= 'G': G(42, 47, 67, 62, 89, 96); ⇒ G(42, 47, 67, 62, 89, 96)

Из фрагмента хорошо видно, что ранее сделанное определение Grodno-переменной отменяется после выполнения restart-предложения. Выполнение restart-предложения следует лишь на внешнем уровне ТД и не рекомендуется внутри ряда его конструкций (функции, процедуры и др.) во избежание возникновения особых и аварийных ситуаций, включая “зависание” пакета, требующее перезагрузки ПК. При этом, следует иметь в виду, что освобождаемая в этом случае память не возвращается операционной среде, а присоединяется к собственному пулу свободной памяти пакета. Поэтому при необходимости получения максимально возможной памяти для решения больших задач пользователю рекомендуется все же производить перезагрузку пакета в Windows-среде. Тогда как второй способ отмены определенности для переменных более универсален. В книге [103] и в прилагаемой к ней Библиотеке представлены средства, обеспечивающие возможность восстановления из процедур исходного состояния объектов. Например, вызов процедуры prestart() очищает все переменные, определенные в текущем сеансе, исключая пакетные переменные.

Предложение присвоения . Идентификатору может быть присвоено любое допустимое Maple-выражение, делающее его определенным; в противном случае идентификатор называется неопределенным, результатом вычисления которого является символьное представление самого идентификатора, что весьма прозрачно иллюстрирует следующий простой пример:

> macro(Salcombe = Vasco): Eesti:= 19.95: Vasco:= Noosphere: Tallinn:= 20.06:

> TRG:= sqrt(Lasnamae*(Eesti + Tallinn)/(Salcombe + Vasco)) + `Baltic Branch`;

Lasnamae

TRG := 4.472694937 + Baltic Branch

Noosphere

Присвоение идентификатору определенного или неопределенного значения производится посредством традиционного (:=)-оператора присвоения вида А:= В, присваивающего левой А-части В-значение. При этом, в качестве левой А-части могут выступать простой идентификатор, индексированный идентификатор или идентификатор функции с аргументами. Точнее, присвоение А-части В-значения корректно, если A имеет assignable-тип, т.е. type(A, 'assignable'); ⇒ true. Вычисленное (или упрощенное) значение В-части присваивается идентификатору А-части.

Оператор присваивания допускает возможность множественного присваивания и определяется конструкциями следующего простого вида:

Id1, Id2, …, Idn := <Выражение_1>, <Выражение_2>, …, <Выражение_n>

При этом, при равном количестве компонент правой и левой частей присвоения производятся на основе взаимно-однозначного соответствия. В противном случае инициируются ошибочные ситуации "Error, ambiguous multiple assignment" либо "Error, cannot split rhs for multiple assignment". Следующий фрагмент иллюстрирует случаи множественного присваивания:

> A, B, C:= 64, 59:

Error, ambiguous multiple assignment

> V, G, S, Art, Kr, Arn:= 64, 59, 39, 17, 10, 44: [V, G, S, Art, Kr, Arn];

[64, 59, 39, 17, 10, 44]

> x, y, z, t, h:= 2006: [x, y, z, t, h];

Error, cannot split rhs for multiple assignment

[x, y, z, t, h]

Примеры фрагмента достаточно прозрачны и особых пояснений не требуют. Сам принцип реализации множественного присваивания также достаточно прост. Вместе с тем, языком не поддерживаются уже достаточно простые конструкции множественного присваивания, что иллюстрируют первый и последний примеры фрагмента.

Между тем, в ряде случаев возникает необходимость назначения того же самого выражения достаточно длинной последовательности имен или запросов функций. Данная проблема решается оператором `&ma`, который имеет идентичный с оператором `:=` приоритет. Оператор `&ma` имеет два формата кодирования, а именно: процедурный и операторный форматы:

&ma('x', 'y', 'z', ..., <rhs>) и ('x', 'y', 'z', ...) &ma (<rhs>)

В общем случае, в обоих случаях в конструкции lhs &ma rhs элементы lhs должны быть закодированы в невычисленном формате, т.е. в апострофах ('). Исключение составляет лишь первый случай присвоения. Кроме того, в операторном формате, левая часть lhs должна быть закодирована в скобках. Кроме того, если правая часть rhs удовлетворяет условию type(rhs, {'..', '<', ` <= `, '.', '*', '^', '+', '='}) = true, то правая часть должна также кодироваться в скобках. Наконец, если необходимо присвоить NULL-значение (т. е. ничего) элементам левой части lhs, то в качестве правой части rhs кодируется _NULL-значение. Успешный вызов процедуры `&ma` или применения оператора &ma возвращает NULL-значение, т. е. ничего с выполнением указанных присвоений. В целом ряде приложений оператор/процедура &ma представляются достаточно полезными [103]. Ниже приведен ряд примеров на применение оператора &ma:

Процедурный формат оператора:

> &ma(h(x), g(y), v(z), r(g), w(h), (a + b)/(c - d)); h(x), g(y), v(z), r(g), w(h);

a + b a + b a + b a + b a + b c − d, c − d, c − d, c − d, c − d

> &ma('x', 'y', 'z', 'g', 'h', "(a + b)/(c - d)"); x, y, z, g, h;

"(a+b)/(c-d)", "(a+b)/(c-d)", "(a+b)/(c-d)", "(a+b)/(c-d)", "(a+b)/(c-d)"

> &ma('x', 'y', 'z', 'g', 'h', _NULL); x, y, z, g, h;

> &ma'x', 'y', 'z', 'g', 'h', 2006); x, y, z, g, h; ⇒ 2006, 2006, 2006, 2006, 2006 > &ma('x', 'y', 'z', 'g', 'h', sin(a)*cos(b)); x, y, z, g, h; sin(a) cos(b), sin(a) cos(b), sin(a) cos(b), sin(a) cos(b), sin(a) cos(b)

Операторный формат:

> ('x', 'y', 'z', 'g', 'h') &ma _NULL; x, y, z, g, h;

> ('x', 'y', 'z', 'g', 'h') &ma 2006; x, y, z, g, h; ⇒ 2006, 2006, 2006, 2006, 2006

> ('x', 'y', 'z', 'g', 'h') &ma (sin(a)*cos(b)); x, y, z, g, h; sin(a) cos(b), sin(a) cos(b), sin(a) cos(b), sin(a) cos(b), sin(a) cos(b) > ('x', 'y', 'z', 'g', 'h') &ma ((a + b)/(c - d)); x, y, z, g, h;

a + b a + b a + b a + b a + b

, , , ,

c − d c − d c − d c − d c − d

Для проверки идентификатора на предмет его определенности используется встроенная функция assigned языка, кодируемая в виде assigned(Идентификатор) и возвращающая значение true в случае определенности идентификатора (простого, индексированного или вызова функции/процедуры), и false-значение в противном случае. При этом, следует иметь в виду, что определенным идентификатор полагается тогда, когда он был использован в качестве левой части (:=)-оператора присвоения, даже если его правая часть являлась неопределенной. Или он получил присвоение по аssign-процедуре. Следующий простой фрагмент иллюстрирует вышесказанное:

> agn:=1947: avz:=grodno: assign(vsv=1967, art=kr): seq(assigned(k), k=[agn, avz, vsv, art]); true, true, true, true

> map(type,[agn, avz, vsv, art],'assignable'); ⇒ [false, true, false, true]

> map(assigned, [agn, avz, vsv, art]);

Error, illegal use of an object as a name

С другой стороны, по конструкции type(Id, 'assignable') мы можем тестировать допустимость присвоения Id–переменной (простой, индексированной или вызова функции/процедуры) выражения: возврат true-значения говорит о такой возможности, false – нет. Следует обратить внимание на последний пример фрагмента, иллюстрирующий некорректность использования встроенной функции map при попытке организации простого цикла.

Вызов функции indets(W {, <Тип>}) возвращает множество всех идентификаторов заданного его первым фактическим W-аргументом Maple-выражения. При этом, W-выражение рассматривается функцией рациональным, т.е. образованным посредством {+, -, *, /}-операций. Поэтому в качестве результата могут возвращаться не только простые идентификаторы W-выражения, но и неопределенные его подвыражения. В случае кодирования второго необязательного аргумента, он определяет тип возвращаемых идентификаторов, являясь своего рода фильтром. В качестве второго фактического аргумента могут выступать как отдельный тип, так и их множество в соответствии с типами, распознаваемыми тестирующей type-функцией языка, рассматриваемой детально ниже. Следующий фрагмент иллюстрирует применение indets-функции для выделения идентификаторов переменных:

> indets(x^3 + 57*y - 99*x*y + (z + sin(t))/(a + b + c) = G);

{sin(t), x, y, z, t, a, b, c, G}

> indets(x^3 + 57*y - 99*x*y + (z + sin(t))/(a + b + c) = G, function); ⇒ {sin(t)} > indets(x^3 + z/y - 99*x*y + sin(t), {integer, name});

{-99, -1, 3, x, y, z, t}

> indets(x^3 + z/y - 99*x*y + sin(t), {integer, name, `*`, `+`});

{-99 -1 3, , , , , , ,x z y t , x3 + − z 99 x y + sin( )t , −99 x y} z

y y

> indets(x^3 + z/y - 99*x*y + sin(t), {algnum, trig});

{-99, -1, 3, sin(t)}

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

Для возможности использования произвольного А-выражения в качестве объекта, которому могут присваиваться значения, Maple-язык располагает evaln-функцией, кодируемой в виде evaln(A) и возвращающей имя выражения. В качестве А-выражения допускаются: простой идентификатор, индексированный идентификатор, вызов функции/ процедуры или конкатенация символов. В результате применения к А-выражению функции evaln оно становится доступным для присвоения ему значений, однако если ему не было сделано реального присвоения, то применение к нему assigned-функции возвращает значение false, т.е. А-выражение остается неопределенным. Таким образом, по конструкции Id:= evaln(Id) производится отмена присвоенного Id-идентификатору значения, делая его неопределенным, как это иллюстрирует следующий простой фрагмент:

> Asv:= 32: assigned(Asv); Asv:= evaln(Asv): Asv, assigned(Asv); true

Asv, false

> Asv:= 67: assigned(Asv); Asv:= 'Asv': Asv, assigned(Asv); true

Asv, false

Из приведенного фрагмента непосредственно следует, что первоначальное присвоение Asv-идентификатору значения делает его определенным, на что указывает и результат вызова assigned-функции. Тогда как последующее выполнение Asv:=evaln(Asv) предложения делает Asv-идентификатор вновь неопределенным. Вторым способом приведения идентификатора к неопределенному состоянию является использование конструкции вида Id:= 'Id', как это иллюстрирует второй пример предыдущего фрагмента.

Для выполнения присвоений можно воспользоваться и assign-процедурой, допускающей в общем случае три формата кодирования, а именно: assign({A, B|A = B|C}), где А – некоторый идентификатор, В - любое допустимое выражение и С - список, множество либо последовательность уравнений. В первых двух случаях применение assign-процедуры эквивалентно применению (:=)-оператора присвоения, тогда как третий случай применяется для обеспечения присвоения левой части каждого элемента списка, множества или последовательности уравнений. Простой фрагмент иллюстрирует вышесказанное:

> assign(AGn, 59); assign(AVz=64); assign([xx=1, yy=2, zz=3, h=cos(y)*tan(z)]);

> assign({xx1=4, yy1=5, zz1=6, y=x*sin(x)}); [AGn, AVz, xx, yy, zz, xx1, yy1, zz1, y, h];

[59, 64, 1, 2, 3, 4, 5, 6, x sin(x), cos(x sin(x)) tan(z)]

> assign('x', assign('y', assign('z', 64))); [x, y, z]; ⇒ [64] # Maple 7 и выше

> assign('x', assign('y', assign('z', 64))); [x, y, z]; # Maple 6 и ниже Error, (in assign) invalid arguments

> `if`(type(AV, 'symbol') = true, assign(AV, 64), false); AV; ⇒ 64

> GS:= op([57*sin(19.95), assign('VG', 52)]) + VG*cos(19.99); ⇒ GS := 72.50422598

Успешное выполнение assign-процедуры производит указанные присвоения и возвращает NULL-значение, в противном случае инициируется соответствующая ошибочная ситуация. Данная ситуация, в частности, возникает при попытке рекурсивного вызова assign-процедуры для релизов 6 и ниже, тогда как в более старших релизах подобного ограничения нет. По отношению к процедуре assign релизы пакета характеризуются весьма существенной несовместимостью, что стимулировало нас к созданию аналога стандартной процедуры, который не только устраняет указанную несовместимость, но и расширяет функциональные возможности [31,39,41-43,45,46,103]. Это и другие наши средства рассмотрены детально в книге [103] и представлены в прилагаемой к ней Библиотеке программных средств.

При этом, следует отметить, что в целом ряде случаев assign-процедура является единственно возможным способом присвоения значений, например, внутри выражений, как это иллюстрирует последний пример фрагмента, который содержит структуры и функции, рассматриваемые ниже. Механизм assign-процедуры достаточно эффективен в различных вычислительных конструкциях, многочисленные примеры применения которого приводятся ниже при рассмотрении различных аспектов Maple-языка, а также в нашей Библиотеке [103].

Обратной к assign-процедуре является unassign-процедура с форматом кодирования: unassign(<Идентификатор_1>, <Идентификатор_2>, ...)

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

> AS:= 39: AV:= 64: AG:= 59: Kr:= 10: Art:= 17: AS, AV, AG, Kr, Art;

39, 64, 59, 10, 17

> unassign(AS, AV, AG, Kr, Art);

Error, (in unassign) cannot unassign '39' (argument must be assignable) > unassign('AS', 'AV', 'AG', 'Kr', 'Art'); AS, AV, AG, Kr, Art;

AS, AV, AG, Kr, Art

> `if`(type(AV, 'symbol') = true, assign(AV, 64), unassign('AV')); AV; ⇒ 64

В приведенном фрагменте пяти переменным присваиваются целочисленные значения, а затем по unassign-процедуре делается попытка отменить сделанные назначения. Попытка вызывает ошибочную ситуацию, обусловленную тем, что в точке вызова процедуре unassign передаются не сами идентификаторы, а их значения (кстати, именно данная ситуация одна из наиболее типичных при ошибочных вызовах assign-процедуры). Для устранения ее идентификаторы следует кодировать в невычисленном формате (кодируя в апострофах), что иллюстрирует повторный вызов unassign-процедуры. Последний пример иллюстрирует применение процедур assign и unassign в условном if-предложении языка, по которому AV-переменной присваивается целочисленное значение, если она была неопределенной, и отменяется ее определение в противном случае.

Для отмены значений имен, имеющих protected-атрибут, может оказаться достаточно полезной и процедура Unassign('n1', 'n2', ...), возвращающая NULL-значение и отменяющая назначения для имен {'n1', 'n2', ...}, закодированных в невычисленном формате. Ее исходный текст представляется однострочным экстракодом следующего вида: Unassign := ( ) → `if`(nargs = 0, NULL, op([unprotect(args), unassign(args)]))

Приведем примеры применения процедур стандартной unassign и нашей Unassign:

> x, y, z, t, h:= 42, 47, 67, 89, 95: protect('x','y','z', 't', 'h'); > unassign('x','y','z', 't', 'h');

Error, (in assign) attempting to assign to `x` which is protected

> x, y, z, t, h; ⇒ 42, 47, 67, 89, 95

> Unassign('x','y','z', 't', 'h');

> x, y, z, t, h; ⇒ x, y, z, t, h

> Unassign();

> unassign();

Error, (in unassign) argument expected

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

В целях защиты идентификаторов от возможных модификаций их определений (назначений) им присваивается protected-атрибут, делающий невозможной какую-либо модификацию указанного типа. Большинство пакетных идентификаторов имеют protectedатрибут, в чем легко убедиться, применяя к ним attributes-функцию, кодируемую в следующем формате: attributes(<Идентификатор>)

и возвращающую значения атрибутов заданного идентификатора, в частности атрибута protected. Если идентификатору не приписано каких-либо атрибутов, то вызов на нем attributes-функции возвращает NULL-значение, т.е. ничего. Для защиты от модификации либо снятия защиты используются процедуры protect и unprotect Maple-языка соответственно, кодируемые в следующем простом формате:

{protect|unprotect}(<Идентификатор_1>, <Идентификатор_2>, ...)

Следующий весьма простой фрагмент Maple-документа иллюстрирует вышесказанное:

> protect(AV_42); attributes(AV_42); ⇒ protected

> unassign(AV_42);

Error, (in assign) attempting to assign to `AV_42` which is protected > AV_42:= 64:

Error, attempting to assign to `AV_42` which is protected

> unprotect(AV_42); attributes(AV_42); AV_42:= 64; ⇒ AV_42 := 64

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

> map(attributes, [Digits, Order, printlevel]); protect('Digits'); protect('Order'); protect('printlevel'); ⇒ []

Error, (in protect) an Environment variable cannot be protected

Error, (in protect) an Environment variable cannot be protected

Error, (in protect) an Environment variable cannot be protected

Хотя по unprotect-процедуре отменяется protected-атрибут любого идентификатора, однако для пакетных идентификаторов этого (по целому ряду причин, здесь не рассматриваемых) делать не рекомендуется.

В целом ряде случаев в качестве весьма полезных средств могут выступать две встроенные функции со следующими форматами кодирования: unames() и anames({ |<Тип>}), возвращающие последовательности соответственно неопределенных и определенных идентификаторов (как пользовательских, так и пакетных), приписанных текущему Maple-сеансу. При этом, для случая anames-функции можно получать подборку определенных идентификаторов, значения которых имеют указанный Тип. Следующий фрагмент иллюстрирует результат вызова указанных выше функций:

> restart; unames();

identical, anyfunc, equation, positive, Integer, restart, radical, And, gamma, neg_infinity, none, default, nonposint, relation, odd, infolevel, indexable, algebraic, SFloat, RootOf, TABLE, float,

real_to_complex, embedded_real, vector, _syslib, realcons, name, assign, INTERFACE_GET, ...

> restart: SV:= 39: GS:= 82: Art:= sin(17): Kr:= sqrt(10): AV:= 64: anames(); sqrt/primes, type/interfaceargs, GS, sqrt, AV, csgn, interface, type/SymbolicInfinity, Art, sin, SV, Kr

> anames('integer'); # Maple 8 sqrt/primes, GS, Digits, printlevel, Order, AV, SV

> anames('environment');

Testzero, UseHardwareFloats, Rounding, %, %%%, Digits, index/newtable, mod, %%, Order, printlevel, Normalizer, NumericEventHandlers

> SV:= 39: GS:= 82: `Art/Kr`:= sin(17): Kr:= sqrt(10): _AV:= 64: anames('user'); # Maple 10

Kr, SV, GS

> anames('alluser'); ⇒ Kr, SV, GS, _AV, Art/Kr

При этом, unames-функция была вызвана в самом начале сеанса работы с пакетом и возвращаемый ею результат представлен только начальным отрезком достаточно длинной последовательности пакетных идентификаторов. Что касается anames-функции, то она в качестве второго необязательного аргумента допускает тип, идентификаторы с которым будут ею возвращаться. При этом, дополнительно к типу и в зависимости от релиза в качестве второго аргумента допускается использование таких параметров как user, environment, alluser, назначение которых можно вкратце охарактеризовать следующим образом. Параметр environment определяет возврат имен предопределенных переменных текущего сеанса пакета, параметр user (введенный, начиная с Maple 9) определяет возврат имен, определенных пользователем, тогда как параметр alluser (введенный, начиная с релиза 10) аналогичен по назначению предыдущему параметру user, но без фильтрации имен пользователя, содержащих прямой слэш «/» и префикс «_» (подчеркивание).

В ряде случаев требуется на основе определенного выражения определить имя (имена), которым в текущем сеансе было присвоено данное выражение. Данная задача решается нашей процедурой nvalue, базирующейся на стандартной процедуре anames и использующей некоторые особенности пакета. Ниже эта процедура будет представлена более детально, здесь же мы приведем лишь фрагмент ее применения, а именно:

> a:= 63: b:= 63: c:= "ransian63": c9:= "ransian63": d:= table([1=63, 2=58, 3=38]): L:= [x, y, z]: assign('h'=`svegal`): t47:= 19.42: t42:= 19.42: R:= a+b*I: B:= a+b*I: Z:= (a1+b1)/(c1+d1): f:=cos(x): g:=proc() `+`(args)/nargs end proc: r:=x..y: m:=x..y: n:= x..y: Lasnamae:= NULL: > Tallinn:=NULL: Grodno:= NULL: Vilnius:= NULL: Moscow:= NULL: map(nvalue, [63, "ransian63", table([1=63,2=58,3=38]), svegal, 19.42, [x,y,z], a+b*I, (a1+b1)/(c1+d1), cos(x), proc () `+`(args)/nargs end proc, x..y]), nvalue(); ⇒ {a, b}, {c9, c}, {d}, {h}, {t47,t42}, {L}, {R,B}, {Z}, {f}, {g}, {m, r, n}, {Tallinn, Grodno, Vilnius, Moscow, Lasnamae}

1.3. Средства Maple-языка для определения свойств переменных

Важным средством управления вычислениями и преобразованиями в среде Maple-языка является assume-процедура и ряд сопутствующих ей средств. Процедура имеет следующие три формата кодирования:

assume(x1, p1, x2, p2, ...) assume(x1::p1, x2::p2, ...) assume(x1p1, x2p2, ...)

и позволяет наделять идентификаторы (переменные) или допустимые Maple-выражения xj заданными свойствами pj, т.е. устанавливать определенные свойства и соотношения между ними. Третий формат процедуры определяет соотношения, налагающие свойство pj на выражение xj. Например, простейшие, но весьма часто используемые вызовы assume(x >= 0) процедуры, определяют для некоторой х-переменной свойство быть неотрицательной действительной константой. Наделяемое по assume-процедуре свойство не является пассивным и соответствующим образом обрабатывается Maple-языком пакета при выполнении вычислений либо преобразований выражений, содержащих переменные, наделенные подобными свойствами. Тестировать наличие приписанного х-переменной свойства можно по вызову процедуры about(x), тогда как наделять х-переменную дополнительными свойствами можно по вызову процедуры additionally(x, Свойство). Следующий фрагмент иллюстрирует вышесказанное:

> assume(x >= 0): map(about, [x, y, z]); ⇒ [ ]

Originally x, renamed x~: is assumed to be: RealRange(0, infinity) y: nothing known about this object z: nothing known about this object

> assume(a >= 0): A:= sqrt(-a): assume(a <= 0): B:= sqrt(-a): [A, B]; ⇒ [ a~ I,

> simplify(map(sqrt, [x^2*a, y^2*a, z^2*a])); ⇒ [x~ −a~ I, y2 a~, z2 a~ ]

> additionally(x, 'odd'): about(x);

Originally x, renamed x~: is assumed to be:

AndProp(RealRange(0, infinity), LinearProp(2, integer, 1))

> map(is, [x, y, z], 'odd', 'posint'); ⇒ [true, false, false]

> assume(y, 'natural'): map(is, [x, y], 'nonnegative'); ⇒ [true, true]

> unassign('x'): about(x);

x: nothing known about this object

> assume(y, {y >= 0, y < 64, 'odd'}): about(y);

Originally y, renamed y~: is assumed to be: {LinearProp(2,integer,1), 0 <= y, y < 64}

> hasassumptions(y); ⇒ true

−a~ ]

Из примеров данного фрагмента, в частности, следует, что переменная с приписанным ей свойством выводится помеченной символом тильды (∼), а по about-процедуре выводится информация о всех приписанных переменной свойствах либо об их отсутствии. Один из примеров фрагмента иллюстрирует влияние наличия свойства положительной определенности переменной на результат упрощения содержащего ее выражения. В другом примере фрагмента иллюстрируется возможность определения для переменной множественности свойств, определяемых как поддерживаемыми языком стандартными свойствами, так и допустимыми отношениями для переменной. При этом, вызов процедуры hasassumptions(x) возвращает true, если на x-выражение было наложено какое-либо соотношение, и false в противном случае.

Режим идентификации assume-переменных определяется showassumed-параметром процедуры interface, принимающим значение {0|1 (по умолчанию)|2}: 0 - отсутствует идентификация, 1 - переменные сопровождаются знаком тильды и 2 - все такие переменные перечисляются в конце выражений, как это иллюстрирует следующий фрагмент:

> assume(V >= 64): assume(G >= 59): S:= V+G; interface(showassumed=0); V + G;

S := V~ + G~

V + G

> assume(Art >= 17, Kr >= 10): interface(showassumed=2): S^2 + (Art + Kr)^2;

S2 + (Art + Kr)2

with assumptions on Art and Kr

> assume(x, 'odd', y::integer, z >= 0), is(x + y, 'integer'); ⇒ true

> x:= 'x': unassign('y'), map(is, [x, y], 'integer'), is(z, 'nonnegative'); ⇒ [false, false], true

До 7-го релиза включительно оперативно переопределять режим идентификации переменных assume можно было переключателями функции Assumed Variables группы Options GUI.

По тестирующей процедуре is(x, Свойство) возвращается true-значение, если х-переменная обладает указанным вторым фактическим аргументом свойством, и значение false в противном случае. При невозможности идентифицировать для х-переменной свойство (например, если она по assume-процедуре свойствами не наделялась) возвращается FAIL-значение. Наконец, отменять приписанные х-переменной свойства можно посредством выполнения простой конструкции x:= 'x' либо вызовом процедуры unassign('x'); сказанное иллюстрируют последние примеры предыдущего фрагмента. При этом, проверять можно как конкретную отдельную переменную, так и выражение по нескольким ведущим переменным и набору искомых свойств.

Maple-язык поддерживает работу со свойствами шести основных групп, а именно:

1) имя свойства, например, continuous, unary;

2) большинство имен типов, например, integer, float, odd, even;

3) числовые диапазоны, например, RealRange(a, b), RealRange(-infinity, b),

RealRange( a, infinity), где a и b могут быть или числовыми значениями или Open(a), где a – числовое значение

4) AndProp(a, b, ...) – and-выражение свойств <a and b and ...>, где a, b, ... – свойства, оп- ределенные выше

5) OrProp(a, b, ...) – or-выражение свойств, где объект может удовлетворять любому из a, b, ... свойств

6) диапазон свойств p1 .. p2, где p1 и p2 - свойства. Данное свойство означает, что объект удовлетворяет по меньшей мере p2, но не более, чем p1; например, integer .. rational удовлетворяется integers/2.

За более детальной информацией по поддерживаемым Maple-языком свойствам остальных групп можно обращаться или к справке по пакету, или к книгам [8-14,78-86,88,105].

Механизм свойств, определяемый процедурой assume и сопутствующей ей группой процедур coulditbe, additionally, is, about, hasassumptions и addproperty, использует специальную глобальную _EnvTry-переменную для определения режима как идентификации у переменных приписанных им свойств, так и их обработки. При этом, данная переменная допускает только два значения: normal (по умолчанию) и hard, из которых указание второго значения может потребовать при вычислениях существенных временных затрат. В текущих реализациях пакета значение глобальной _EnvTry-переменной, определяющей режим обработки переменных, наделенных assume-свойствами, не определено, что иллюстрирует следующий достаточно прозрачный фрагмент:

> _EnvTry, about(_EnvTry); ⇒ _EnvTry

_EnvTry: nothing known about this object

> assume(V >= 64): about(V);

Originally V, renamed V~: is assumed to be: RealRange(64, infinity)

> `if`(is(V, RealRange(64, infinity)), ln(V) + 42, sqrt(Art + Kr)); ⇒ ln(V~) + 42 > assume(47 <= G, G <= 59): about(G);

Originally G, renamed G~: is assumed to be: RealRange(47, 59)

> `if`(is(G, RealRange(47, 59)), [10, 17, Sv, Art, Kr], Family(x, y, z)); ⇒ [10, 17, Sv, Art, Kr]

> assume(x >= 0), simplify(sqrt(x^2)), simplify(sqrt(y^2)); ⇒ x~, csgn(y) y

> sqrt(a*b), sqrt(a^2), assume(a >= 0, b <= 0), sqrt(a*b), sqrt(a^2); ⇒ a b, a2 , −a~ b~ I a~,

Механизм приписанных свойств является достаточно развитым и мощным средством как числовых вычислений, так и символьных вычислений и преобразований. Использование его оказывается весьма эффективным при программировании целого ряда важных задач во многих приложениях. Последние примеры предыдущего фрагмента иллюстрируют некоторые простые элементы его использования в конкретном программировании. Тогда как конкретный assume-механизм базируется на алгоритмах Т. Вейбеля и Г. Гоннета. С интересным обсуждением принципов его применения, реализации и ограничений можно довольно детально ознакомиться в интересных работах указанных авторов, цитируемых в конце справки по пакету (см. ?assume), и цитируемых в них многочисленных источниках различного назначения.

В ряде случаев требуется выполнить обмен значениями между переменными. Например, переменные х, у и z, имеющие значения 64, 59 и 39 должны получить значения 39, 59 и 64 соответственно. Следующая процедура varsort решает данную задачу.

varsort := proc() local a b c d k, , , , ;

`if`(nargs = 0, RETURN( ), assign(c = [ ])); d := proc(x y, )

try if evalf( )x < evalf( )y then true else false end if catch : `if`(map(type, {x, y}, 'symbol') = {true}, lexorder(x, y), true) end try

end proc ;

a := [seq `if` type(( ( args[ ]k , 'boolproc'), assign(' 'b = args[ ]k ),

`if`(type(args[ ]k , 'symbol'), op(args[ ]k , assign(' 'c = [op( )c , k])), NULL)), k = 1 .. nargs)];

a := sort(a, `if`(type(b, 'boolproc'), ,b d)); for k to nops(a) do assign(args[c k[ ]] = a k[ ]) end do

end proc

> x:=64: y:=59: z:=39: m:=vsv: n:=avz: p:=agn: varsort('x', 'z', 'y', 'm', 'p', 'n'), x, y, z, m, n, p;

39, 64, 59, agn, vsv, avz

> x:=64: y:=59: z:=39: m:=vsv: n:=avz: p:=agn: varsort('x', 'y', 'z', 'm', 'n', 'n'), x, y, z, m, n, p;

39, 59, 64, avz, vsv, agn

Вызов varsort('x', 'y', 'z', …) возвращает NULL-значение, обеспечивая обмен значениями между переменными x, y, z, … в порядке возрастания. Подробнее с возможностями данной процедуры можно ознакомиться в [41-43,103,109].

1.4. Типы числовых и символьных данных Mapleязыка пакета

Средства Maple-языка поддерживают работу как с простыми, так и сложными типами данных числового или символьного (алгебраического) характера. В первую очередь рассмотрим типы простых данных числового характера, предварив краткой информацией по очень важным встроенным функциям nops и op, непосредственно связанных со структурной организацией Maple-выражений. Первая функция возвращает число операндов выражения, заданного ее единственным фактическим аргументом. Тогда как вторая имеет более сложный формат кодирования следующего вида: op({ |n,|n..m,|<Список>,} <Выражение>)

где первый необязательный фактический аргумент определяет возврат соответственно: n-го операнда, с n-го по m-й операнды или операнды согласно Списка их позиций в порядке возрастания уровней вложенности Выражения. При этом, при n = 0 возвращается тип самого выражения, а в случае отсутствия указанного первым аргументом операнда инициируется ошибочная ситуация. Для случая n<0 выполняется соотношение op(n,V) ≡ op(nops(V) + n + 1, V), где V – выражение, а для неопределенного Id-идентификатора имеют место соотношения: op(0, Id) ⇒ symbol и op(1, Id) ⇒ Id. Отсутствие первого аргумента op-функции аналогично вызову вида op(1 .. nops(V), V). Для вывода структурной организации произвольного выражения может оказаться полезной конструкция вида: op('k', <Выражение>) $'k'=0 .. nops(<Выражение>)

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

> Art:= 3*sin(x) + 10*cos(y)/AV + sqrt(AG^2 + AS^2)*TRG: op('k', Art)$'k'=0 .. nops(Art);

10 cos(y) 2 + AS2 TRG

+, 3 sin( )x , , AG

AV

Ниже будет рассмотрено достаточно средств Maple-языка, ориентированных на задачи символьной обработки выражений, включая и те, которые базируются на их структурном ана-изе. Целый ряд средств для решения подобных задач предоставляет и наша Библиотека [103]. Нам же для дальнейшего будет пока вполне достаточно информации по встроенным функциям nops и op пакета.

Целые (integer); представляют собой цепочки из одной или более цифр, максимальная длина которых определяется используемой платформой ЭВМ: так для 32-битной она не превышает 524280 цифр, а для 64-битной – 38654705646 цифр. Целые могут быть со знаком и без: 1999, -57, 140642. На числах данного типа функции op и nops возвращают соответственно значение числа и значение 1, тогда как функция type идентифицирует их тип как integer, например:

> op(429957123456789), nops(429957123456789); ⇒ 429957123456789, 1

> type(429957123456789, 'integer'); ⇒ true

Действительные (float) с плавающей точкой; представляют собой цепочки из десятичных цифр с десятичной точкой в {начале|середине|конце} цепочки; числа данного типа допускают следующие два основных формата кодирования:

[<знак>]{<целое>.<целое>|.<целое>|<целое>.}

Float([<знак>]<мантисса>, [<знак>]<экспонента>) ≡

[<знак>]<мантисса>.{E|e}[<знак>]<экспонента>

В качестве мантиссы и экспоненты используются целые со знаком или без; при этом, мантисса может иметь любую длину, но экспонента ограничивается длиной машинного слова: для 32-битной платформы значение экспоненты не превышает целого 2147483647, а для 64-битной платформы – целого значения 9223372036854775807. Тогда как максимальное допустимое число цифр мантиссы аналогично максимальному допустимому числу цифр целого (integer) числа. Число цифр мантиссы, участвующих в операциях арифметики с плавающей точкой, определяется предопределенной Digits-переменной ядра пакета, имеющей по умолчанию значение 10. В книге [103] представлен ряд полезных средств, позволяющих оформлять числовые значения в принятых для документирования и печати форматах.

Второй способ кодирования действительных чисел применяется, как правило, при работе с очень большими или очень малыми значениями. Для конвертации значений в действительный тип используется evalf-функция, возвращающая значение float-типа. В вышеприведенных примерах применение данной функции уже иллюстрировалось; функция имеет простой формат кодирования evalf(<Выражение> [, n]) и возвращает результат float-типа вычисления выражения (действительного или комплексного) с заданной n-точностью (если она определена вторым фактическим аргументом). Maple испытывает затруднения при вычислениях уже целого ряда простых радикалов с рациональными степенями от отрицательных значений, если знаменатель экспоненты – нечетное число. В этом случае даже стандартная функция evalf оказывается бессильной совместно с использованием пакетного модуля RealDomain, что очень хорошо иллюстрируют довольно простые примеры, а именно:

> R:=(58 + (-243)^(1/5) + (-8)^(2/3))*(10 + (-1331)^(2/3) + (-8)^(2/3))/((63 +(-32)^(4/5) - (-27)^

(2/3))* (17 + (343)^(2/3) + (-32)^(3/5))); with(RealDomain): evalf(R), Evalf(R);

R := (58 + (-243)((14//55)) + (-8)(2(/32)/3))(10 + (-1331)(2/3()2/3) + (-8)(3(/52)/3))

(63 + (-32) − (-27) ) (17 + 343 + (-32) )

-0.7722517003 + 1.867646862 I, 1.961822660

Тогда как процедура Evalf, находящаяся в упомянутой Библиотеке [103], вполне успешно решает данную задачу, что и иллюстрируе данный пример в среде Maple 10.

Практически все встроенные функции пакета возвращают результаты float-типа, если хоть один из их аргументов получает значение данного типа. Автоматически результат операции возвращается float-типа, если один из операндов имеет данный тип. По умолчанию число цифр выводимого действительного числа равно 10, но в любое время может переопределяться в глобальной Digits-переменной ядра пакета. Примеры: 1.42, -.57, 17., 8.9, 2.9E-3, -5.6e+4, -5.4E-2. При этом, конструкции типа <целое>.{E|e}<целое> вызывают синтаксическую ошибку с диагностикой "missing operator or `;`".

Третьим способом определения действительных чисел является функция Float(<мантисса>, <экспонента>), возвращающая число float-типа с указанными мантиссой и экспонентой, например: Float(2006, -10); ⇒ 0.2006e-6. Иногда Float-функцию называют конструктором float-чисел.

Действительное число имеет два операнда: мантиссу и экспоненту, поэтому вызов функции op({1|2}, <число>) соответственно возвращает {мантиссу|экспоненту} указанного ее вторым аргументом числа, тогда как вызов функции nops(<число>) возвращает значение 2 по числу операндов; при этом, функция type идентифицирует тип таких чисел как float, например:

> op(2, 19.95e-10), op(1, 19.95e-10), nops(19.95e-10); ⇒ -12, 1995, 2 > type(19.95e-10, 'float'); ⇒ true

Вопросы арифметики с числами float-типа детально рассматриваются, например, в книгах [12,13] и в ряде других изданий, поэтому ввиду наших целей здесь они не детализируются. Впрочем, данный тип числовых выражений и так достаточно прозрачен.

Рациональные (rational); представляют собой числа (дроби), кодируемые в форме вида [<знак>] a/b, где a и b - целые числа; в частности, целые числа также рассматриваются пакетом в качестве частного случая рациональных, имеющих единичный знаменатель. Для перевода рациональных чисел в числа float-типа используется упомянутая выше evalf-функция, например: -2, 64, 59/47, -2006/9, evalf(59/47)=1.255319149. На числах данного типа функции op и nops возвращают соответственно значение числителя, знаменателя и значение 2 по числу операндов, тогда как функция type идентифицирует тип таких чисел как fraction или rational, например:

> op(350/2006), nops(350/2006); ⇒ 175, 1003, 2

> type(350/2006, 'fraction'), type(350/2006, 'rational'); ⇒ true, true

Для работы с рациональными числами Maple-язык располагает целым рядом функциональных средств, достаточно детально рассматриваемых ниже.

Комплексные (complex); представляют собой числа вида a+b*I (b ≠ 0), где a и b – числа рассмотренных выше трех типов, а I – комплексная единица (I = √-1). Части a и b комплексного числа называются соответственно действительной и мнимой; отсутствие второй делает число действительным. Примеры: -19.42 + 64*I, 88.9*I, 57*I/42. Для комплексных чисел различаются целые, действительные, рациональные и числовые в зависимости от того, какого типа их действительная и мнимая части. Например, число 64 - 42*I полагается комплексным целочисленным, тогда как числовое комплексное предполагает числовыми действительную и мнимую части. На числах комплексного типа функции op и nops возвращают соответственно значения действительной и мнимой частей, и число 2 операндов, тогда как функция type идентифицирует тип таких чисел как complex (может указываться и подтип), например:

> op(64 - 42*I), nops(64 - 42*I); ⇒ 64, -42, 2

> type(64 - 42*I, 'complex'('integer')); ⇒ true

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

> type(I*I, ‘complex’), type(I^2, ‘complex’), type(I^4, ‘complex’), type(52 + b*I, ‘complex’({‘symbol’, ‘integer’})), type(a + 57*I, ‘complex’({‘symbol’, ‘integer’})); true, true, true, true, true

> type(I*I, ‘complex1’), type(I^2, ‘complex1’), type(I^4, ‘complex1’), type(52 + b*I, ‘complex1’({‘symbol’, ‘integer’})), type(a + 57*I, ‘complex1’({‘symbol’, ‘integer’})); false, false, false, true, true

Согласно соглашениям пакета вызов функции type(x, complex) возвращает true, если x – выражение формы a+b*I, где a (при наличии) и b (при наличии) конечны, имея тип realcons. В принципе, с формальной точки зрения все нормально. Однако в целом ряде случаев необходимо точно идентифицировать комплексный тип, имеющий форму a + b*I при b ≠ 0. С этой целью нами был дополнительно определен тип complex1 [103], решающий данную задачу. В предыдущем фрагменте можно сравнить результаты тестирования на типы complex и complex1.

Булевские (boolean); представляют собой логические значения true (истина), false (ложь) и FAIL (неопределенная истинность). Третье значение используется в случае, когда истинность какого-либо выражения неизвестна. Функция type идентифицирует тип таких значений как boolean, например:

> map(type, [true, false, FAIL], ‘boolean’); ⇒ [true, true, true]

Язык Maple использует трехзначную логику для выполнения операций булевой алгебры. Булевы выражения образуются на основе базовых логических операторов {and, or, not} и операторов отношения {<, <=, >, >=, =, <> (не равно)}. Наша Библиотека [103] определяет ряд достаточно полезных средств, расширяющих стандартные средства пакета для работы с булевой алгеброй.

Константы (constant); представляют собой постоянные значения любого из вышерассмотренных пяти типов. Данные конструкции весьма прозрачны и особых пояснений, так же как и иллюстрирующих их примеров, не требуют.

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

(1) целые числа со знаком (1942; -324; 34567; -43567654326543; 786543278);

(2) действительные со знаком (19.95; -345.84; 7864.87643; -63776.2334643);

(3) рациональные со знаком (18/95; -6/28; -4536786/65932; 765987/123897);

(4) комплексные числа (48+53*I; 28.3-4.45*I; ½+5/6*I; -4543.87604+53/48*I)

Каждый из перечисленных типов числовых данных идентифицируется специальным идентификатором: integer – целые; float – действительные с плавающей точкой; rational, fraction – рациональные (дроби вида m/n; m, n – целые) и complex – комплексные числа. Каждый из этих идентификаторов может быть использован для тестирования типа переменных и выражений посредством type-функции, уже упоминаемой выше, но детально рассматриваемой ниже.

Для обеспечения работы с числовыми значениями Maple-язык располагает как функциями общего назначения, так и специальными, ориентированными на конкретный числовой тип. Например, по функциям ifactor, igcd, iperflow возвращается соответственно: разложение на целочисленные множители целого числа, наибольший общий делитель целых чисел и результат проверки целого числа на возможность представления его в виде np , где n и p – оба целые числа, например: [ifactor(64), iperfpow(625, 't'), igcd(42, 7)], t; [(2)6 , 25, 7], 2. Детально как с общими, так и специальными функциями работы с числовыми выражениями можно ознакомиться в книгах [12,103], в других изданиях, но наиболее полно в справке по пакету.

Наряду с десятичными числовыми значениями пакет поддерживает работу с бинарными, 8- и 16-ричными, а также произвольными q-ричными числами (q - основание системы счисления). Для преобразования чисел из одной системы счисления в другую служит функция convert языка, рассматриваемая ниже. Наряду с числовыми данными, пакет поддерживает работу с нечисловыми (символьными, алгебраическими) выражениями, характеризуемыми тем, что им не приписаны какие-либо числовые значения. С символьными данными и их обработкой познакомимся детальнее несколько позднее. Здесь лишь отметим базовый тип символьных данных – данные типов string и symbol.

Строка (string); любая конечная последовательность символов, взятая в верхние двойные кавычки; данная последовательность может содержать и специальные символы, как это иллюстрирует следующий простой пример:

“Dfr@t4#\78578”; “A_V_Z; A+G-N; “” 574%!@#$%”; “_Vasco&Salcombe_2006”

На строках функции op и nops возвращают соответственно саму строку и количество 1 операндов, тогда как функция type идентифицирует тип таких выражений как string, например:

> op(“123456”), nops(“123456”), type(“123456”, ‘string’); ⇒ “123456”, 1, true

Символ (symbol, name); любая конечная последовательность символов, взятая в верхние обратные кавычки; данная последовательность может содержать и специальные символы, как это иллюстрирует следующий простой пример:

`Dfr@t4#\78578`; `A_V_Z; A+G-N; “” 574%!@#$%`; `_Vasco&Salcombe_2006`; AVZ

Между тем, в отличие от строк, требующих обязательного ограничивания их двойными кавычками, для символов ограничения верхними обратными кавычками требуется только в том случае, когда они содержат специальные символы, например, пробелы. На символах функции op и nops возвращают соответственно сам символ и число 1 операндов, тогда как type-функция идентифицирует тип таких выражений как symbol либо name, например:

> op(`123456`), nops(`12345`), map2(type,`123456`, [‘symbol’, ‘name’]); ⇒ 12345, 1, [true, true]

В отличие от 4-го релиза последующие релизы Maple четко различает понятия символа и строки. Символьный тип играет основополагающую роль в символьных (алгебраических) вычислениях и обработке информации. Строчные данные, наряду с символьными, играют основную роль при работе с символьной информацией и Maple-язык располагает для работы с ними довольно развитыми средствами, которые с той или иной степенью полноты рассматриваются нами ниже. Немало дополнительных средств для работы с выражениями типов {symbol, name, string} представлено и нашей Библиотекой [103]. Многие из них позволяют весьма существенно упростить программирование целого ряда приложений в среде пакета Maple релизов 8 – 10.

Например, процедура _ON(expr) обеспечивает конвертацию алгебраического выражения expr в стилизованный формат, в котором каждое float-число представляется в виде n.m.

_ON := proc(expr) local a b c d p k, , , , , ; assign(a = cat " ",( convert(expr, 'string'), " " ,) b = {seq(convert(k, 'string'), k = 0 .. 9)}), assign(d = Search2(a, {"."}));

d := [seq `if`( (a[d k[ ] − 1] = " " and a[d k[ ] + 1] = "." or a[d[k] − 1] = "." and a[d[k] + 1] = " " or type(a[d[k] + 1], 'letter') or type(a[d k[ ] − 1], 'letter') or

member(a[d k[ ] + 1], b) and member(a[d k[ ] − 1], b), NULL, d k[ ]), k = 1 .. nops( )d )];

p := [1 $ (k = 1 .. nops(d))];

for k to nops(d) do if not member(a[d k[ ] − 1], b) then a := cat(a[1 .. d k[ ] − 1], "0", a[d k[ ] .. -1]); d := p + d

elif not member(a[d k[ ] + 1], b) then a := cat(a[1 .. d k[ ] − 1], a[d k[ ] + 1 .. -1]); d := d − p

end if

end do; came( )a

end proc

> R:= 8.*a + 47*I+(( .5 + 8.*a +c . d) + cos(6.*gamma + x-7.) - 3.*7^(-2.*a) + int(f(x), x=1. .. .5))/

(( 8.^a.b - .8*Pi )*sqrt( a - 3. ) + exp(8.*a - 87.*Pi + 16.*I) + array(1..2, 1..2, [[1., .2], [3., 4.]]));

0.5

0.5 + 8. a + (c d . ) + cos(6. γ + − x 7.) − 3. 7( 2. a) + ⌠ f( )x dx

1.

R := 8. a + 47 I +

(((8. )a . )b − 0.8 π) a − 3. + e(8. a − 87. π + 16. I) + 1.3. 0.24.  

> _ON(R);

0.5

0.5 + 8 a + (c d . ) + cos(6 γ + − x 7) − 3 7( 2 a) + ⌠ f( )x dx

1

8 a + 47 I +

(((8 )a . )b − 0.8 π) a − 3 + e(8 a − 87 π + 16 I) + 13 0.24 

При этом, результат, возвращаемый процедурой _ON, пригоден для непосредственных вычислений. Между тем, данная процедура носит описательный характер, не изменяя самой сути вычисления выражений.

Тогда как процедура Evalf(x) совместно с процедурой ffp устраняет затруднения Maple при вычислениях ряда простых радикалов х с рациональными степенями от отрицательных значений, если знаменатель их показателя – нечетное число [41-43,103,109].

Evalf := proc(E::algebraic) local a b c d h g k, , , , , , , ψ; assign(a = convert(E, 'string'), g = [ ]), assign(b = Search2(a, {")^("})); ψ := x → `if`(type(x, 'integer') or x = 0, parse(cat(convert(x, 'string'), ".")), x); if b = [ ] then nulldel(evalf(E, `if`(nargs = 2 and type(args[2], 'posint'), args[2], NULL)))

;

ψ(%)

else

for k in b do

c := a[nexts(a, k, "(", 1)[-1] .. nexts(a, k, ")")[-1]]; try d := map(convert, map(parse, SLD(c, "^")), 'fraction') catch : next end try ;

if type(d[1], 'negative') and type(d[2], 'fraction') and

type(denom(d[2]), 'odd') then if type(numer(d[2]), 'even') then g := [op(g), c = cat "(", [( c 3 .. -1])]

else g := [op(g), c = cat "(-1)*(", [( c 3 .. -1])] end if

end if

end do;

nulldel evalf parse(( ( SUB_S(g a, )),

`if`(nargs = 2 and type(args 2[ ], 'posint'), args 2[ ], NULL))); ψ (%)

end if

end proc

1.5. Базовые типы структур данных Maple-языка

Наряду с простыми данными Maple-язык поддерживает работу с наиболее распространенными структурами данных такими как последовательности, списки, множества, массивы и таблицы. Вкратце остановимся на каждой из перечисленных структур.

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

Sequence := B1, B2, B3, ..., Bn; Bj (j=1..n) – любое допустимое выражение языка

Последовательность не является ни списком, ни множеством, но она лежит в основе определения этих типов структур данных пакета. Пример: GLS:= 47, Gr, -64*L, `99n+57`, F. Структура типа последовательность выражений или просто последовательность (exprseq), как уже отмечалось, образуется (,)-оператором разделителя выражений (запятая) и представляет интерес не только в качестве самостоятельного объекта в среде Maple-языка, но и в качестве основы таких важных структур как: функциональная, список, множество и индексированная. Важным свойством данного типа структуры данных является то, что если ее элементы также последовательности, то результатом является раскрытая единая последовательность, как это иллюстрирует следующий простой пример:

> AV:= a, b, c, d, e: GS:= x, y, z: Sv:=2, 9: AG:= 1, 2, 3, AV, 4, Sv, 5, 6, GS; AG := 1, 2, 3, a, b, c, d, e, 4, 2, 9, 5, 6, x, y, z

Длина произвольной SQ-последовательности (число ее элементов) вычисляется по конструкции nops([SQ]), тогда как ее k-й элемент получаем посредством оператора выделения SQ[k]. Оператор выделения имеет весьма простой вид: Id[<Выражение>], где Id – идентификатор структуры типа массив, список, множество или последовательность; значение выражения Id[k] определяет искомый элемент структуры. Если Id не определен, то он возвращается индексированным, например:

> SQ:= V, G, S, Art, Kr, Arne: {SQ[4], nops([SQ]), R[S]}; ⇒ {6, R[S], Art}

> SQ:= [SQ]: SQ[6]:= Aarne: SQ:= op(SQ): SQ; ⇒ V, G, S, Art, Kr, Aarne

> Z:= SQ: whattype(SQ); ⇒ exprseq

> type(Z, 'exprseq');

Error, wrong number (or type) of parameters in function type > hastype(Z, 'exprseq');

Error, wrong number (or type) of parameters in function hastype

Так как по конструкции вида SQ[k]:=<Выражение> недопустимо присвоение заданного выражения k-му элементу SQ-последовательности, то для этих целей можно воспользоваться цепочкой Maple-предложений вида: SQ:=[SQ]:SQ[k]:= <Выражение>: SQ:=op(SQ):, как это иллюстрирует второй пример последнего фрагмента. При этом, следует иметь в виду, что тип последовательности тестируется только whattype-процедурой языка, т. к. при передаче последовательности в качестве аргумента другим тестирующим функциям она рассматривается как последовательность фактических аргументов. Последние три примера предыдущего фрагмента иллюстрируют сказанное. Для прямого тестирования структур типа `expressions sequence` нами была определена процедура typeseq, описанная в книге [103] и включенная в прилагаемую к ней Библиотеку [109]. Ниже дано несколько примеров ее применения, а именно:

> typeseq("Kr",Art,6,14,"RANS",IAN,Tallinn, 'seqn'(integer, string, symbol)); ⇒ true

> typeseq(a, b, 10, 17, 'seqn'), typeseq("Kr", Art, 10, 17, "RANS", IAN, Tallinn, Vilnius, 'seqn'), typeseq(G, [a], {b}, 61, 10/17, 'seqn'('integer', 'symbol', 'list', 'set', 'fraction'));

true, true, true

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

<Выражение_1> .. <Выражение_n>

где вычисляемые выражения определяют соответственно нижнюю и верхнюю границы ран-жирования с шагом 1. В зависимости от места использования ранжированной конструкции для значений выражений допускаются значения типов {float, integer}. Особо самостоятельного значения ранжированное выражение не имеет и используется в качестве ранжирующей компоненты во многих стандартных Maple-конструкциях (последовательностные структуры, суммирование, произведение, интегрирование и др.). В качестве самостоятельного применения оператор ранжирования можно использовать, например, с ||оператором конкатенации для создания последовательностей, например:

> n:= 1: m:= 9: SL:= x||(n .. m); ⇒ SL := x1, x2, x3, x4, x5, x6, x7, x8, x9

> A||("g" .. "s"); ⇒ Ag, Ah, Ai, Aj, Ak, Al, Am, An, Ao, Ap, Aq, Ar, As

> A||("м" .. "э"); ⇒ Ам, Ан, Ао, Ап, Ар, Ас, Ат, Ау, Аф, Ах, Ац, Ач, Аш, Ащ, Аъ, Аы, Аь, Аэ

> GS(x||(1 .. 5)):= (x1*x2*x5 + sin(x4 + x5))*(x1^2 + x2^2 + x3^2 + x4^2);

GS(x1, x2, x3, x4, x5) := (x1 x2 x5 + sin(x4 + x5)) (x1 + x2 + x3 + x4)

> A||(1 .. 3)||(4 .. 7); ⇒ A14, A15, A16, A17, A24, A25, A26, A27, A34, A35, A36, A37

Из приведенного фрагмента четко прослеживается одно существенное отличие бинарного ||-оператора конкатенации от других бинарных операторов Maple-языка. Если стандартным в языке является порядок вычисления выражений «слева направо», то для ||-оператора конкатенации используется обратный ему порядок вычислений. В последнем примере вычисляется сначала правый операнд, а затем левый. Более того, из второго и третьего примеров явствует, что наряду с числовыми значениями для ранжированной конструкции допускаются и буквы английского и национальных алфавитов, закодированные в строчном формате. На ранжированных выражениях функции op и nops возвращают соответственно последовательность их левых и правых частей, и число 2 операндов, тогда как функция type идентифицирует тип таких выражений как range, например:

> op(a .. b), nops(a .. b); ⇒ a, b, 2

> type("a" .. "z", 'range'), type(1 .. 64, 'range'), type(-1 .. -6.4, 'range'); ⇒ true, true, true

Детальнее структуры типа последовательность и оператор ранжирования будут рассматриваться ниже в различных контекстах, включая иллюстративные фрагменты. Здесь же мы лишь упомянем два функциональных средства Maple-языка, преднаначенных для определения последовательностных структур данных, а именно: $-оператор и seq-функция, которые имеют соответственно следующие наиболее общего вида форматы:

V(k) $ k = p .. n; ⇒ V(p), V(p + 1), ... ,V(n) seq(V[k], k = p .. n); ⇒ V(p), V(p + 1), ... ,V(n)

где V – допустимое Maple-выражение, в общем случае зависящее от к-переменной ранжирования. Следующий простой фрагмент иллюстрирует примеры структур типа последовательности, образованные $-оператором и seq-функцией Maple-языка:

> G(h) $ h = 9.42 .. 14.99; ⇒ G(9.42), G(10.42), G(11.42), G(12.42), G(13.42), G(14.42)

> seq(G(x), x = 9.42 .. 14.99); ⇒ G(9.42), G(10.42), G(11.42), G(12.42), G(13.42), G(14.42)

> 67 $ 15; ⇒ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67

> Gs $ h = -0.25 .. 7.99; ⇒ Gs, Gs, Gs, Gs, Gs, Gs, Gs, Gs, Gs

> cat(seq(x, x = "a" .. "z")); ⇒ "abcdefghijklmnopqrstuvwxyz"

> cat(seq(x, x = "A" .. "Z")); ⇒ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

> H:= [x, y, z, h, r, t, k, p, w, z, d]: H[s] $ s=1 .. 11; ⇒ x, y, z, h, r, t, k, p, w, z, d

> seq(H[s], s = 1 .. 11); ⇒ x, y, z, h, r, t, k, p, w, z, d

С учетом сказанного приведенный фрагмент весьма прозрачен и особых пояснений не требует. Более детально вопросы, связанные с $-оператором и seq-функцией, будут рассмотрены в связи с обсуждением функциональных средств языка. Здесь лишь отметим, что при всей близости обоих средств между ними существует различие, выражающееся в более универсальном характере seq-функции в плане ее применимости для генерации последовательностей, что будет проиллюстрировано ниже на примерах и детально рассмотрено в [12,41-43,103].

Список (list) – списочная структура, широко используемая пакетом для организации вычислений и обработки разнообразной информации, образуется посредством помещения последовательности выражений в квадратные скобки, формируя конструкцию следующего общего вида:

List := [B1, B2, B3, ..., Bn], где Bj (j=1 .. n) – любое допустимое выражение Maple-языка

Длина списка определяется числом входящих в него элементов, а идентичными полагаются списки, имеющие одинаковую длину и одинаковые значения соответствующих элементов. По конструкции вида List[n] можно получать значение n-го элемента списка с List-идентификатором, а на основе вызова функции nops(List) – число его элементов. При этом, по вызову функции op(List) можно конвертировать списочную List-структуру в последовательность. Замена к-го элемента списка L выполняется по конструкции L[k] := <Выражение>, тогда как для его удаления используется конструкция subsop(k = NULL, L). Функция type идентифицирует тип списочных структур как list. Следующий простой пример иллюстрируют вышесказанное:

> L:=[sqrt(25),September,2,2006,GS]: [L[4],nops(L)],op(L); ⇒ [2006,5], 5,September,2, 2006,GS

> L[2]:= October: L; L:= subsop(3 = NULL, L): L, type(L, 'list'); ⇒ [5, October, 2, 2006, GS] [5, October, 2006, GS], true

Между тем, для удаления к-го элемента списка L с последующим обновлением списка «на месте» можно использовать конструкцию вида L[k] := 'NULL', весьма удобную в целом ряде приложений. Однако данный подход, корректно работая в одиночных вычислениях, не дает искомого результата в циклических. Поэтому во втором случае после каждого вычисления вида L[k]:= 'NULL' следует использовать операцию присвоения L:= L. Следующий весьма простой фрагмент иллюстрирует вышесказанное:

> restart; L:= [q, w, e, r, t, y, u, j, o, p, a, d, g, h, k, z, x]: L[10]:= 'NULL': L;

[q, w, e, r, t, y, u, j, o, a, d, g, h, k, z, x]

> L:=[q,w,e,r,t,y,u,j,o,p,a,d,g,h,k,z,x]: while L <> [] do L[1]:= 'NULL': L:= L end do: L ⇒ []

На основе предложенного приема можно предложить несложную процедуру mlist, обновляющую список на месте. Ее исходный текст и примеры применения даются ниже.

> L:= [q,w,e,r,t,y,u,j,o,p,d,a,s,k,x,z,y]: subsop(5 = VSV, L), subsop(10 = NULL, L), L;

[q, w, e, r, VSV, y, u, j, o, p, d, a, s, k, x, z, y], [q, w, e, r, t, y, u, j, o, d, a, s, k, x, z, y],

[q, w, e, r, t, y, u, j, o, p, d, a, s, k, x, z, y]

> mlist:= proc(L::uneval, a::posint, b::anything) if type(eval(L), 'list') then if belong(a, 1 ..

nops(eval(L))) then L[a]:= b; L:=eval(L); NULL else error "in list <%1> does not exist

element with number %2", L, a end if else error "1st argument should be a list but had received <%1>", whattype(eval(L)) end if end proc;

mlist := proc(L::uneval, a::posint, b::anything) if type(eval(L), 'list') then if belong(a, 1 .. nops(eval(L))) then L a[ ] := b; L := eval(L); NULL

else error "in list <%1> does not exist element with number %2", L a,

end if

else error

"1st argument should be a list but had received <%1>"whattype(, eval(L)) end if

end proc

> mlist(L, 10, 'NULL'), L; ⇒ [q, w, e, r, t, y, u, j, o, d, a, s, k, x, z, y]

> mlist(L, 16, 'NULL'), L; ⇒ [q, w, e, r, t, y, u, j, o, d, a, s, k, x, z]

> mlist(L, 3, AVZ), L; ⇒ [q, w, AVZ, r, t, y, u, j, o, d, a, s, k, x, z]

> L:= [q,w,e,r,t,y,u,j,o,p,d,a,s,k,x,z,y]: while L <> [] do mlist(L,1,'NULL') end do: L; ⇒ [] > mlist(L, 64, AGN), L;

Error, (in mlist) in list <L> does not exist element with number 59 > mlist(B, 64, AGN), L;

Error, (in mlist) 1st argument should be a list but had received <symbol>

Вызов процедуры mlist(L, a, b) возвращает NULL-значение, заменяя a-й элемент списка L на Maple-выражение b; при этом, обновление списка производится «на месте». Тогда как вызов процедуры mlist(L, a, 'NULL') также возвращает NULL-значение, удаляя a-й элемент из списка L и обновляя список «на месте». В ряде приложений процедура mlist представляется достаточно полезной.

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

> Avz:= [[[1, 2, 3], [5, 6]], [6, G], V, S]: op(Avz), nops(Avz); ⇒ [[1, 2, 3], [5, 6]], [6, G], V, S, 4

Для вложенных списков, чьи элементы имеют одинаковую длину, Maple определяет тип listlist, играющий весьма важную роль при организации индексированных структур (матрица, массив и др.). Нами дополнительно определен тип nestlist [103], характеризующий более общий тип вложенности списков, например:

> Avz:= [[[1, 2, 3], [5, 6]], [6, G], V, S]: type(Avz, 'listlist'), type(Avz, 'nestlist'); ⇒ false, true > Agn:= [[1, 2, 3], [a, b, c], [x, y, z]]: type(Agn, 'listlist'), type(Agn, 'nestlist'); ⇒ true, true Пустой список обозначается как L0:=[] и nops(L0); ⇒ 0, op(L0); ⇒ NULL. Maple-язык располагает весьма обширным набором функциональных средств для работы со списочными структурами, которые будут рассматриваться нами довольно детально ниже. Пакетный модуль ListTools содержит набор средств для работы со списками. В свою очередь, наша Библиотека [103] также содержит ряд полезных средств для такого типа структур.

Списочные структуры являются весьма широко используемыми пакетом объектами (исходные и выходные данные, управление порядком вычислений, представление массивов, матриц и тензоров и др.). При этом, в достаточно широких пределах допускается сочетание их с другими типами данных и структур данных.

Множество (set) – структура данных, весьма широко используемая пакетом, в первую очередь, для организации вычислений в традиционном теоретико-множественном смысле, образуется посредством помещения последовательности в фигурные скобки, формируя конструкцию следующего простого вида:

Set := {B1, B2, B3, ..., Bn}, где Bj (j=1 .. n) – любая допустимая конструкция Maple-языка

Мощность множества определяется числом входящих в него элементов, а идентичными полагаются множества, имеющие одинаковый набор вычисленных значений элементов без учета их кратности. По конструкции вида Set[n] можно получать значение n-го элемента множества с Set-идентификатором, а на основе вызова функции nops(Set) - число его элементов. Более того, по вызову функции op(Set) можно конвертировать Set-множество в последовательность. При этом, функция type идентифицирует тип множественных структур как set. Следующий пример иллюстрируют вышесказанное:

> Set1:= {q,6/3,e,r,5,t,w}: Set2:= {q,w,e,r,2,t,w,sqrt(25),r,q,q}: [op(Set1)], [op(Set2)],

[nops(Set1)], [nops(Set2)], Set2[5]; ⇒ [2, 5, r, q, e, t, w], [2, 5, r, q, e, t, w], [7], [7], e

Из приведенного примера можно заметить ряд особенностей применения функций op и nops к структуре данных set-типа. Прежде всего, производится вычисление элементов множества и их упорядочивание согласно соглашениям пакета, поэтому порядки элементов исходного множества и результата его вычисления могут не совпадать. Это же относится и к мощности множества - в результате вычисления его элементов одинаковые результаты сводятся к одному, например: S := {56/2, 7*4, 28, 7*2^2}: nops(S); ⇒ 1. Пустое множество обозначается как S0:= {} и nops(S0); ⇒ 0, op(S0); ⇒ NULL.

Maple-язык поддерживает ряд операций над множествами, аналогичных классическим теоретико-множественным операциям, включая базовые операции объединения (union), разности (minus) и пересечения (intersect) множеств, например:

> S1:= {q,r,64,t,w,x,h,z}: S2:= {h,n,v,x,59,z,k,s}: S1 union S2, S1 intersect S2, S2 minus S1;

{59, 64, x, h, s, z, n, r, v, q, t, w, k}, {x, h, z}, {59, s, n, v, k}

Множества могут иметь различный уровень вложенности, определяя различного рода структуры данных и конструкции. Нами дополнительно определен тип setset [103], аналогичный стандартному типу listlist для случая списков, например:

> map(type, [{{}}, {{},{}}, {{{7}}}, {{a, b}, {c, d}}, {{a, b}, {c}}, {{10, 17}, {64, 59}, {39, 44}}], 'setset');

[true, true, true, true, false, true]

Ввиду принципиально различных принципов упорядочения элементов списка и множества для замены элементов множества можно использовать два способа, а именно: (1) по номеру элемента и (2) по его значению, как это иллюстрирует следующий фрагмент:

(1) S := S minus {S[k]} union {<Выражение>} (2) S := subs(Sk = <Выражение>, S)

где S – произвольное множество и S[k], Sk – его к-й элемент и значение к-го элемента соответственно. Конкретные примеры иллюстрируют применение обоих способов:

> S:= {q, r, 64, t, w, x, h, z}; ⇒ S := {64, x, h, z, r, q, t, w}

> S:= S minus {S[4]} union {Avz}; ⇒ S := {64, x, h, r, q, t, w, Avz}

> S:= subs(Avz = Agn, S); ⇒ S := {64, x, h, r, q, t, w, Agn}

> S:= subs(Agn = NULL, S); ⇒ S := {64, x, r, h, q, t, w}

Одной из наиболее полезных и часто используемых над объектами типов set и list является map-функция Maple-языка, имеющая в общем виде формат кодирования:

map(F, f(x, y, z, ...), a, b, c, …) ⇒ f(F(x, a, b, c, …), F(y, a, b, c, …), F(z, a, b, c, …), ...)

где F – некоторая функция или процедура, f – выражение от переменных x, y, z, ... и a, b, c, … – некоторые выражения, и возвращающая результат применения функции или процеду-ры F к каждому аргументу f(x, y, z, ...)-конструкции (а в более общей постановке, к каждому операнду выражения), как это иллюстрирует следующий простой фрагмент:

> map(F, [x, y, z], a, b, c, d); ⇒ [F(x, a, b, c, d), F(y, a, b, c, d), F(z, a, b, c, d)]

> map(F, {x, y, z}, a, b, c, d); ⇒ {F(x, a, b, c, d), F(y, a, b, c, d), F(z, a, b, c, d)}

> map(F, f(x, y, z), a, b, c, d); ⇒ f(F(x, a, b, c, d), F(y, a, b, c, d), F(z, a, b, c, d))

> Seq:= x, y, z: map(F, Seq, a, b, c, d); ⇒ F(x, y, z, a, b, c, d)

Между тем, если по map(F, V, a1, ..., an)-функции применяется определенная ее первым фактическим F-аргументом функция к каждому операнду V-выражения с передачей ей дополнительных фактических aj-аргументов (j=1..n), то по ее модификации – функции map2 вида map2(F, a1, V, a2, ..., an) производится применение к V-выражению F-функции со специфическим a1-аргументом и возможностью передачи ей дополнительных aj-аргументов (j=2 .. n), что является существенным расширением map-функции. Следующий пример иллюстрирует принцип формального выполнения map2-функции:

> map2(F, a1, [x,y,z], a, b, c, d, e); ⇒ [F(a1, x, a, b, c, d, e),F(a1, y, a, b, c, d, e),F(a1, z, a, b, c, d, e)]

> map2(S, x, G(a, b, c), y, z, h, t); ⇒ G(S(x, a, y, z, h, t), S(x, b, y, z, h, t), S(x, c, y, z, h, t))

> map(F, W, x, y, z, t, h), map2(F, W, x, y, z, t, h); ⇒ F(W, x, y, z, t, h), F(W, x, y, z, t, h)

Из данного фрагмента несложно усматривается не только общий принцип выполнения функции map2, но и ее эквивалентность map-функции на определенных наборах аргументов. И выше, и в дальнейшем обе эти функции достаточно часто используются в иллюстративных примерах, лучше раскрывая припцип своего выполнения. Нами был определен ряд новых процедур map3, map4, map5, map6, mapN, mapLS, mapTab (расширяющих возможности функций map и map2), описанных в нашей книге [103] и включенных в состав прилагаемой к ней Библиотеке [109]. Maple-язык располагает обширным набором средств для работы с множествами и списками, которые будут рассматриваться нами детальнее ниже в различных контекстах. Более того, данный набор дополнен нашими средствами работы со списочными структурами [103,109].

Массив (array) – структура данных, весьма широко используемая Maple, в первую очередь, при работе с матричными и векторными объектами. Массив представляет собой определенное обобщение понятия списочной структуры, когда ее элементам приписываются индексы, идентифицирующие местоположение элементов в структуре. При этом, размерность массива может быть более единицы, а сами индексы элементов могут принимать как положительные, так и отрицательные значения, например:

> M:= array(-10 .. 16, [k$k = 1 .. 27]): M[-7], M[-1], M[0], M[16]; ⇒ 4, 10, 11, 27

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

M := array(<Индексная функция>, <Размерность>, <Начальные значения>) где Индексная функция определяет специальный индексный атрибут (например, symmetric атрибут определяет соотношение M[n, m]=M[m, n]). Размерность задается в виде интервалов “а .. в” по каждому из индексов массива и Начальные значения задают исходные значения для элементов массива. При этом, следует иметь в виду, что Maple-язык автоматически не определяет элементы массива, поэтому сразу же после создания массива его элементы являются неопределенными, например: a:= array(1..3): print(a); ⇒ [a1, a2, a3]. Обращение к массиву производится по его идентификатору, а к его элементам в виде индексированного идентификатора, например: print(M); M[5, 6, 2]. Вывод массива на печать производится по print-функции, тогда как по функциям eval и evalm не только предоставляется возможность вывода содержимого массива, но и возврата его в качестве результата для возможности последующего использования в вычислениях. Применение данных функций проиллюстрировано ниже. Ни один из трех аргументов функции array не является обязательным, однако должен присутствовать по меньшей мере один из двух последних для возможности построения массива. Наиболее простой формат array-конструкции имеет следующий вид:

M := array(J11 .. J1n, J21 .. J2n, ..., Jm1 . .Jmn {, []| })

определяя пустой (неопределенный) М-массив mxn-размерности. Переопределение элементов M-массива производится по конструкции вида M[p, k, h, ...]:=<Выражение>, которая остается действительной и для списочных структур. В целом ряде случаев при определении массива целесообразно сразу же задавать начальные значения для его элементов полностью или частично, что иллюстрирует следующий простой фрагмент:

> G:= array(1..3, 1..3, []): G[1,1]:= 42: G[1,2]:= 47: G[1,3]:= 67: G[2,1]:= 89: G[2,2]:= 96: G[2,3]:= 99: G[3,1]:= 95: G[3,2]:= 59: G[3,3]:= 62: print(G);

42 8995 47 9659 67 9962 

> assign(MMM = [eval(G), evalm(G)]), MMM, sum(MMM[k], k = 1..2);

  42 9589 47 5996 67 9962 ,  42 8995 47 9659 67 9962   , 2 _addmultmp

> eval(_addmultmp), map(sqrt, _addmultmp);

 428995 479659 679962 ,   

> S:= array(1..3, 1..3, [[a, b, c], [d, e, f], [g, h, k]]): map([eval, evala, evalm], S);

[[[ad d dg g g,, ,, ,a, a]]] [[[bh h he e e,,, ,b,, b]]] [[[ck k kf f f,, ,, ,c, c]]]

> Art:=array(symmetric, 1..4, 1..4): Art[1, 1]:= 42: Art[1, 2]:= 47: Art[1, 3]:= 67: Art[1, 4]:= 62:

Art[2, 2]:= 57: Art[2, 3]:= 89: Art[2, 4]:= 96: Art[3, 3]:= 52: Art[3, 4]:= 99: Art[4, 4]:= 9:

> whattype(eval(Art)), type(Art, 'array'); ⇒ array, true

> [op(0, eval(Art))], [op(1, eval(Art))], [op(2, eval(Art))], [op(3, eval(Art))];

[array], [symmetric], [1 .. 4, 1 .. 4], [[(1, 1) = 42, (2, 2) = 57, (4, 4) = 9, (1, 2) = 47, (3, 3) = 52, (2, 4) = 96, (2, 3) = 89, (1, 4) = 62, (1, 3) = 67, (3, 4) = 99]]

Как следует из приведенного фрагмента в первом примере определяется пустой G-массив с последующим прямым определением его элементов путем присваивания; в качестве разделителей предложений выбрано двоеточие, чтобы не загромождать документ выводом промежуточных результатов. Затем над полученным G-массивом производятся простые операции, о которых говорилось выше. Во втором примере определяется S-массив той же (3х3)-размерности с одновременным заданием начальных значений для всех его элементов. Результаты определения массива используются для выполнения нестандартной процедуры, использующей рассмотренные выше функции и которая может оказаться полезной в практической работе с объектами типа array. Там же иллюстрируется применение map-функции для поэлементной обработки массива. При определения начальных значений элементов непосредственно в array-конструкции они представляются в виде вложенного списка listlist-типа, структура которого должна строго соответствовать структуре определяемого массива, как это иллюстрируется фрагментом. В качестве встроенных индексных атрибутов пакет использует: symmetric, antisymmetric, sparse, diagonal, identity и ряд определяемых пакетными модулями, которые детально обсуждаются при рассмотрении матричных объектов, например, в нашей книге [12].

При этом, массив одновременно выводится и возвращается по функциям evalm и eval, тогда как по print-функции производится только вывод массива на экран. Предыдущий фрагмент иллюстрирует сказанное. Наряду с этим, примеры фрагмента иллюстрируют использование функции op для получения типа Art-объекта, индексной функции (первый аргумент array-функции), размерности массива (второй аргумент) и содержимого всех начальных значений для входов массива (третий аргумент). Иллюстрируется и применение map-функции для поэлементной обработки массива.

Ниже обсуждение структур данных array-типа будет детализироваться и рассматриваться на протяжении последующих глав книги. При этом, следует иметь в виду то обстоятельство, что частным случаем понятия массива являются векторы (одномерный массив) и матрицы (двухмерный прямоугольный массив), для обеспечения работы с которыми Maple-язык располагает достаточно развитым набором средств, прежде всего, определяемых пакетным модулем linalg, ориентированным на решение задач линейной алгебры [11-14,80-89]. Ниже этот вопрос будет рассмотрен несколько детальнее.

Массив hfarray-типа . Для обеспечения численных вычислений на основе машинной арифметики с плавающей точкой (МАПТ) Maple-язык поддерживает новый тип структуры данных – массивы hfarray-типа, определяемые hfarray-функцией формата вида:

Mhf := hfarray({<Размерность>}{, <Начальные значения>})

где назначение формальных аргументов функции полностью аналогично случаю функции array; при этом, оба аргумента необязательны. Размерность определяется одним либо несколькими ранжированными выражениями, при ее отсутствии используется установка по умолчанию. Пределы изменения индексов по каждому измерению массива определяются используемой платформой и для 32-битной платформы находятся в диапазоне от -2147483648 до 2147483647 и для 64-битной платформы от -9223372036854775808 до 9223372036854775807.

Данные массивы составляют специальный hfarray-тип, распознаваемый тестирующими функцией type и процедурой whattype. Массивы данного типа в качестве значений своих элементов содержат числа с плавающей точкой двойной точности стандарта IEEE-754. При этом, поддерживаются три специальные значения данного стандарта: +Infinity, Infinity и Quiet NaN, последнее из которых определяется как `undefined`. Для hfarrayфункции в качестве начальных значений могут выступать любые допустимые Mapleвыражения, результатом вычисления которых являются числа float-типа, или значения undefined, infinity, -infinity. Полностью массивы hfarray-типа возвращаются и выводятся на экран соответственно по функциям eval и print. Следующий фрагмент иллюстрирует вышесказанное:

> МАПТ:= hfarray([evalf([sqrt(10), sqrt(17)]), evalf([sqrt(64), sqrt(59)])]);

3.16227766000000000 4.12310562600000008

МАПТ :=  

 8. 7.68114574799999960

> МАПТ:= hfarray([[sqrt(10), sqrt(17)], [sqrt(64), sqrt(59)]]); Error, unable to store '10^(1/2)' when datatype=float[8]

> type(МАПТ,'hfarray'), type(МАПТ,'array'), whattype(eval(МАПТ)); ⇒ true, false, hfarray

> eval(МАПТ), print(МАПТ);

3.162277660000000008. 4.123105626000000087.68114574799999960  

3.162277660000000008. 4.123105626000000087.68114574799999960  

> evalf(array([[sqrt(10), sqrt(17)], [sqrt(64), sqrt(59)]]));

3.1622776608. 4.1231056267.681145748  

Структуры данных hfarray-типа широко используются с evalhf-функцией, поддерживающей МАПТ, и достаточно детально рассматриваются, например, в [11,12]. При этом, следует иметь в виду, что работа с такого типа массивами вне рамок МАПТ не эффективна, ибо требуется выполнение соглашений между МАПТ и Maple-арифметикой с плавающей точкой. Последний пример фрагмента иллюстрирует различия между массивами типов array и hfarray при одной и той же установке предопределенной Digits-переменной пакета. Тогда как второй пример фрагмента иллюстрирует недопустимость определения элементов hfarray-массива типами данных, отличными от extended_numericтипа (обобщение типов numeric, undefined либо infinity, -infinity). Для устранения данного недостатка используется evalf-функция.

Таблица (table) – структура данных, весьма широко используемая Maple, прежде всего, при работе с различного рода табличными объектами. Таблица представляет собой определенное обобщение понятия двумерного массива, когда в качестве значений индексов массива могут использоваться не только целочисленные значения, но и произвольные выражения, в первую очередь, символьные и строчные значения в качестве названия строк и столбцов. Характерной чертой таблицы является возможность работы со структурами данных, включающими естественные нотации (фамилии, имена, названия и т.д.). Итак, в общем случае Т-таблица определяется конструкцией следующего простого формата:

T := table(<Индексная функция>, <Список/множество начальных значений>) где Индексная функция определяет специальный индексный атрибут (например, symmetricатрибут), аналогичный случаю массива, и второй аргумент определяет исходные значения для элементов таблицы (ее входы и выходы). Пустая таблица определяется по конструкции вида T:= table(); при этом, первый аргумент table-функции необязателен.

Второй аргумент table-функции определяется, как правило, в виде списка или множества, элементами которого могут быть как отдельные допустимые Maple-выражения, так и уравнения, т.е. конструкции вида “A=B”. Левые А-части уравнений выступают в качестве идентификаторов строк таблицы и определяют ее входы, а правые – выходы, т. е. по конструкции формата T[<Вход>] возвращается строка-выход, отвечающая указанному в качестве значения аргумента Входу. Поэтому, если T:=table([X1=Y1, X2=Y2, ...]), то T[Xk]; ⇒ Yk. Если в списке/множестве начальных значений хоть один из элементов не является уравнением, то Maple в качестве входов в таблицу использует целые неотрицательные числа (1 .. <число строк>), располагая при этом строки таблицы в определенном порядке, отличном от естественного. Это одна из причин, почему в качестве элементов второго аргумента функции table следует кодировать уравнения, т.е. это не тот случай, когда решение вопроса стоит отдавать на откуп пакету. В качестве правых В-частей уравнений списка/множества функции могут выступать произвольные Maple объекты, позволяя создавать достаточно сложные табличные структуры данных, примером чего может служить следующий весьма простой фрагмент:

> T:= table([A=[AV, 42, 64, 350], B=[42, 64, array([[RANS, IAN], [REA, RAC]])],

C=array([[{G, S}, {Kr, Ar}], [{Vasco}, {Salcombe}]])]): eval(T);

table([C = {{VascoS, G}} {Salcombe{Kr, Ar} }, B = 42 64, , RANSREA

A = [AV, 42 64 350, , ]

])

IAN,

RAC

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

> Tab[Grodno]:= 1962: Tab[Tartu]:= 1966: Tab[Tallinn]:= 1999: Tab[Gomel]:= 1995:

Tab[Moscow]:= 1994: eval(Tab), print(Tab);

table([Grodno = 1962, Tartu = 1966, Tallinn = 1999, Gomel = 1995, Moscow = 2006]) table([Grodno = 1962, Tartu = 1966, Tallinn = 1999, Gomel = 1995, Moscow = 2006])

> (Tab[Grodno]-Tab[Tallinn]-Tab[Moscow])/(Tab[Tartu]-Tab[Gomel]-350); ⇒ 2043/379 > Tab[Grodno], Tab[Tartu], Tab[Tallinn], Tab[Gomel], Tab[Moscow];

1962, 1966, 1999, 1995, 2006

> whattype(Tab), whattype(eval(Tab)), type(Tab, 'table'), type(eval(Tab), 'table'), map2(type, Tab, ['matrix', 'array']); ⇒ symbol, table, true, true, [false, false]

> op(0, eval(Tab)), [op(1, eval(Tab))], op(2, eval(Tab)); table, [], [Grodno = 1962, Tartu = 1966, Tallinn = 1999, Gomel = 1995, Moscow = 2006]

Обращение к таблице производится по ее идентификатору, а к ее элементам в форме индексированного идентификатора, например: Т[С]. Вывод таблицы на печать производится по функции print (применение которой проиллюстрировано выше); это же относится и к выводу ее отдельных выходов, если они не являются конструкциями базовых типов {число, строка, список, множество}. При этом, подобно массиву таблица одновременно выводится и возвращается по функции eval, тогда как к ней не применимы функции evalm и evala. Приведенный выше фрагмент иллюстрирует сказанное. Наряду с этим, примеры фрагмента иллюстрируют использование op-функции для получения типа Tab-объекта, индексной функции (первый аргумент table-функции) и содержимого всех входов таблицы. По причине отсутствия индексной функции вызов op(1, eval(Tab)) возвращает NULL-значение.

Наиболее простой формат table-функции имеет следующий вид:

T := table([X1, X2, X3, ..., Xn]) или T := table({X1, X2, X3, ..., Xn})

определяя Т-таблицу из n строк, идентифицируемых входами-числами. Переопределение элементов Т-таблицы производится по конструкциям вида: Т[<вход>]:=<Выражение>, которые работают и со списочными структурами. Для удаления из Т-таблицы выхода (соответствующего заданному входу) выполняется следующая конструкция: Т[вход]:=NULL, тогда как для удаления всех выходов произвольной Т-таблицы достаточно выполнить следующее простое Maple-предложение цикла:

for k in [<Список входов>] do T[k]:= NULL end do:

либо эквивалентное ему выражение следующего вида:

eval(parse(convert(subs(F = null, mapTab(F, T, 1)), 'string')))

где null и mapTab – процедуры из Библиотеки, описание которых находится в [103]:

> Tab[Grodno]:= 1962: Tab[Tartu]:= 1966: Tab[Tallinn]:= 1999: Tab[Gomel]:= 1995: Tab[Moscow]:= 2006: eval(parse(convert(subs(F = null, mapTab(F, Tab, 1)), 'string'))); table([Tartu = (), Tallinn = (), Gomel = (), Moscow = (), Grodno = ()])

При работе с массивами и таблицами наиболее часто используемыми являются две функции indices и entries, имеющие следующий простой формат кодирования; {indices|entries}(T), где Т – массив или таблица

и возвращающие индексы/входы и соответствующие им элементы/выходы массива/таблицы Т соответственно, как это иллюстрирует следующий простой фрагмент:

> Tab[Grodno]:= 1962: Tab[Tartu]:= 1966: Tab[Tallinn]:= 1999: Tab[Gomel]:= 1995:

Tab[Moscow]:= 2006: indices(Tab), entries(Tab);

[Tartu], [Tallinn], [Gomel], [Moscow], [Grodno], [1966], [1999], [1995], [2006], [1962]

> A:= array([[a, b, h], [c, d, k], [x, y, z]]): indices(A), entries(A);

[1, 1], [2, 2], [2, 3], [2, 1], [3, 1], [3, 2], [1, 2], [1, 3], [3, 3], [a], [d], [k], [c], [x], [y], [b], [h], [z]

> with(Tab); eval(%);

[Gomel, Grodno, Moscow, Tallinn, Tartu]

[1995, 1962, 2006, 1999, 1966]

> Tab1[1942]:= 1962: Tab1[2006]:= 1966: type(Tab1, 'table'); with(Tab1); ⇒ true Error, (in pacman:-pexports) invalid arguments to sort

> map(op, [indices(Tab)]), map(op, [entries(Tab)]);

[Tartu, Tallinn, Gomel, Moscow, Grodno], [1966, 1999, 1995, 2006, 1962]

В частности, последний пример фрагмента представляет конструкции, полезные при работе, прежде всего, с таблицами, и позволяющие представлять их входы и выходы в виде списка. Во фрагменте (пример 3) проиллюстрировано также использование процедуры with (применяемой, как правило, лишь с пакетными и программными модулями) для получения списка входов таблицы. При этом, следует учитывать, что если в качестве входов Т-таблицы выступают значения {symbol|name}-типа, то по выхову with(T) возвращается их отсортированный лексикографически список, в противном случае возникает ошибочная ситуация, как это иллюстрирует пример 4 предыдущего фрагмента.

Имея многочисленные приложения, табличные структуры используются и для организации пакетных и/или библиотечных модулей. Такой подход особенно широко использовался в ранних релизах пакета и об этом детальнее будет сказано ниже. Представим здесь один простой пример такого подхода. В этом случае входами таблицы Т являются имена процедур, а ее выходами – соответствующие им определения. Тогда вызов таким образом погруженных в табличную структуру процедур принимают следующий простой вид, а именно: Т[имя](Аргументы). Следующий фрагмент иллюстрирует сказанное:

> T:= table([sr = (() -> `+`(args)/nargs), (ds = (() -> sqrt(sum((args[k] - sr(args))^2, k=1..nargs)/nargs)))]);

( )

T sr  nargs  ds ( ) → nargs k = 1 (argsnargsk − sr args( ))2 ]) := table([ = ( ) → `+` args , =

> with(T), 6*T[sr](64,59,10,17,39,44), 6*T[ds](64,59,10,17,39,44); ⇒ [ds sr, ], 233, 14249

> save(T,"C:\\Temp\\lib.m"); restart; read("C:\\Temp\\lib.m"); with(T); ⇒ [ds, sr]

> 6*T[sr](64,59,10,17,39,44), 6*T[ds](64,59,10,17,39,44); ⇒ 233, 14249

Данный подход эффективен, например, когда требуется создавать средства модуля пакета табличного типа поэтапно – экспорты (входы таблицы). В этом случае одноименные входы заменяют соответствующие им выходы, тогда как новые входы просто добавляются в таблицу. Данный подход имеет и другие привлекательные особенности.

Относительно структур типа массив (array) и таблица (table) следует сделать одно существенное пояснение. Все используемые Maple структуры, кроме этих двух, обладают свойством ненаследования, т.е. для них справедливы следующие соотношения:

X:= a: Y:= X: Y:= b: [X, Y]; ⇒ [a, b]

т.е. присвоение X-значения Y-значению с последующим переопределением второго не изменяет исходного X-значения. В случае же структур типа массив и таблица это вполне естественное свойство не соблюдается. Поэтому Maple-язык располагает специальной процедурой copy(<Массив/Таблица>), позволяющей создавать копии указанного массива/таблицы, модификация которой не затрагивает самого оригинала. Следующий весьма простой фрагмент иллюстрирует вышесказанное:

> X:= a: Y:= X: Y:= b: [X, Y]; ⇒ [a, b]

> A:= array(1..5, [42, 47, 67, 88, 96]); ⇒ A := [42, 47, 67, 88, 96]

> C:= copy(A); ⇒ C := [42, 47, 67, 88, 96]

> C[3]:= 39: C[5]:= 10: [A[3], A[5]]; ⇒ [67, 96]

> B:= A: B[3]:= 95: B[5]:= 99: [A[3], A[5]]; ⇒ [95, 99]

Создание по copy-процедуре копий объектов указанных двух типов позволяет модифицировать только их копии без изменения самих объектов-оригиналов.

В качестве встроенных индексных атрибутов Maple для table-функции используются: sparse, symmetric, antisymmetric, diagonal и identity (а также определяемые пакетными модулями), детальнее обсуждаемые при рассмотрении матричных объектов. Обсуждение tableтипа структуры данных будет постоянно детализироваться и многоаспектно рассматриваться на протяжении последующих глав книги. Целый ряд полезных средств для работы с табличными объектами представляет и наша Библиотека, прилагаемая к книге [103] и находящаяся в свободном доступе по адресам [109].

В завершение рассмотрения структур данных, поддерживаемых пакетом, будет вполне уместно представить и связанные с объектами array-типа, уже упоминаемые матрицы и векторы. Матрицы являются весьма широко применимым понятием в целом ряде естественно-научных дисциплин, составляя самостоятельный объект исследования современной математики – теорию матриц, выходящую за рамки традиционного университетского курса высшей алгебры. Основу теории матриц составляет алгебра квадратных матриц, для обеспечения которой Maple-язык располагает целым рядом важных и полезных средств, поддерживаемых функциональными средствами – модулей LinearAlgebra и linalg. Данные модули содержат определения 118 и 114 процедур соответственно (в зависимости от релиза пакета; пример приведен для Maple 10), обеспечивающих расширенные средства линейной алгебры по работе с матричными и векторными выражениями. Данные средства не входят в задачу настоящей книги, акцентирующей внимание на вопросах собственно программирования в Maple, поэтому здесь мы лишь представим основные структуры, с которыми имеют дело средства линейной алгебры пакета, а именно: матрицы (matrix) и векторы (vector).

Матрицы (matrix); в среде Maple-языка матрица представляется в виде 2D-массива, нумерация строк и столбцов которого производится, начиная с единицы. Матрица определяется либо явно через рассмотренную выше array-функцию, например M:= array(1..n, 1..p), или посредством процедуры matrix, имеющей следующие два простых формата кодирования, а именно:

matrix(L) и matrix(n, p, {L|Fn|Lv})

где L – вложенный список (listlist) векторов элементов матрицы, n и p – число ее строк и столбцов, Fn – функция генерации элементов матрицы и Lv – список или вектор элементов матрицы. Функция Fn генерации элементов матрицы определяется в форме пользовательских функции или процедуры, например Fn:=(k,j) -> Ф(k,j), такой, что M[k,j]:= Fn(k,j). Процедура randmatrix модуля linalg позволяет стохастически генерировать матрицы (mxn)-размерности, что удобно, например, для различного рода отладок и тестов.

По тестирующей функции type(M, {matrix|'matrix'(K{, square})}) возвращается true-значение, если М-выражение является матрицей; при этом допускается проверка на принадлежность значений ее элементов заданной К-области и/или квадратности (square) матрицы. В противном случае возвращается false-значение. Так как матрица является одним из типов более общего понятия массив, то функция type(M, 'array') также возвращает true-значение, если М - матрица. Обратное же в общем случае неверно, например, в случае одномерных массивов-векторов. По функции convert(L, 'matrix') возвращается матрица, если L - соответствующая ей вложенная списочная структура. С другой стороны, по функции convert(M, 'multiset') производится конвертация матричной структуры в структуру вложенных списков, каждый элемент-список которой содержит три элемента, где первые два определяют координаты элемента матрицы, а третий – его значение. Следующий фрагмент иллюстрирует вышесказанное.

> M:= matrix(3, 3, [x, y, z, a, b, c, V, G, S]);

M :=

Vax Gby Scz

> type(M, 'array'), type(M, 'matrix'), type(M, 'matrix'(symbol, square)); ⇒ true, true, true > convert([[x, y, z], [a, b, c], [V, G, S]], 'matrix');

V ax G by S cz 

> convert(M, 'multiset');

[[3, 2, G], [2, 2, b], [1, 1, x], [1, 3, z], [1, 2, y], [2, 1, a], [3, 1, V], [2, 3, c], [3, 3, S]]

> convert((a+b)*x/(c+d)*y, 'multiset'); ⇒ [[a+b, 1], [x, 1], [c+d, -1], [y, 1]]

Следует отметить, что функция convert(M, 'multiset') имеет более широкое применение, допуская в качестве своего первого фактического М-аргумента любое Maple-выражение (при этом, трактовка составляющих возвращаемого ею вложенного списка весьма существенно зависит от типа М-выражения), однако наибольший смысл она имеет для М-выражений типа массив или содержащих термы-множители. Не отвлекаясь на частности, рекомендуем читателю определить влияние типа М-выражения на семантику возвращаемого функцией вложенного списка, тем более, что в ряде случаев это может оказаться весьма полезным приемом в практическом программировании.

Обращение к элементам М-матрицы производится по индексированной M[k, j]-конструкции, идентифицирующей ее (k, j)-й элемент. Посредством этой конструкции элементам М-матрицы можно как присваивать значения, так и использовать их в вычислениях в качестве обычных переменных. Следовательно, данная индексированная конструкция обеспечивает адресное обращение к элементам М-матрицы. Совместное использование функций map и F-функции/процедуры позволяет применять последнюю одновременно ко всем элементам М-матрицы, не идентифицируя их отдельно, т.е. имеет место следующее определяющее соотношение: map(F, M {, <опции>}) ≡ ∀(k)∀(j) (M[k, j] := F(M[k, j] {, <опции>}))

Например:

> M:= matrix(3, 3, [x, y, z, a, b, c, V, G, S]): map(F, M, Art, Kr, Arn);

FFF(((V Art Kr Arna Art Kr Arnx,,, Art,,,Kr,,,Arn))) FFF(((G Art Kr Arnb Art Kr Arny,,,Art,,,Kr,,,Arn))) FFF(((S Art Kr Arnc Art Kr Arnz,,,Art,,, Kr,,, Arn)))

В целом же М-матрица рассматривается как единый объект и для его вывода или возвращения в матричной нотации можно использовать соответствено функции print и {op|evalm}. Однако на основе адресного подхода обработку матриц можно производить и рассмотренными функциональными средствами, ориентированными на сугубо скалярные аргументы. В частности, по конструкции numboccur(eval(M), <Элемент>) можно тестировать наличие в указанной M-матрице заданного элемента. Позволяет это делать и наша процедура belong [103,109].

С другой стороны, целый ряд процедур позволяют обрабатывать М-матрицу как единый объект; такие функции будем называть матричными, т.е. в качестве одного из ведущих аргументов их выступает идентификатор матрицы. Язык Maple располагает достаточно обширным набором матричных процедур, характеристика которых весьма детально рассмотрена в книгах [8-14,55-60,86-88] с той или иной степенью охвата и в полном объеме в справке по пакету. Лишь некоторые из них будут рассмотрены ниже. По функции evalm(VM) возвращается результат вычисленим VM-выражения, содержащего матрицы, применяя функциональные map-преобразования над матрицами. Однако, применение evalm-функции требует особой осмотрительности, ибо Maple-язык перед передачей фактических аргументов evalm-функции может производить их упрощения (предварительные вычисления), в ряде случаев приводящие к некорректным с точки зрения матричной алгебры результатам. Например, по evalm(diff(M, x)) возвращается нулевое значение, тогда как предполагается результат дифференцирования М-матрицы. Это связано с тем обстоятельством, что отличные от матричных функции воспринимают идентификатор матрицы неопределенным. В VM-выражении все неопределенные идентификаторы полагаются evalm-функцией в зависимости от их использования символьными матрицами либо скалярами. В суммах, содержащих матрицы, все скалярные константы рассматриваются умноженными на единичную матрицу, что позволяет естественным образом определять матричные полиномы. Так как операция произведения матриц некоммутативна, то для нее следует использовать матричный (&*)-оператор произведения, приоритет которого идентичен приоритету (*)-оператора скалярного произведения. В качестве операндов бинарного матричного (&*)-оператора могут выступать матрицы либо их идентификаторы. Следующий фрагмент иллюстрирует способы определения, тестирования и вычисления матричных выражений:

> M1:= convert([[42, 47], [67, 89]], 'matrix'): M2:=matrix(2, 3, [x, x^2, x^3, x^3, x^2, x]):

> M3:= matrix([[T, G], [G, M]]): M4:=matrix(2, 2, [ln(x), x*sin(x), Catalan*x^2, sqrt(x)]): > map(type, [M1, M2, M3, M4], 'matrix'); ⇒ [true, true, true, true]

> map(op, [M1, M2, M3, M4]);

4267 4789, xx3 xx22 xx3, GT MG, Catalan xln(x) 2 x sin (x)

> [map(diff, M2, x), map(int, M4, x)];

31x2 22 xx 31x2, Catalan xx ln(3x) − x3 sin(x2) − x3(x3/2cos) (x)

> evalm(10*M1^2 + 17*M1);

4984488909 11221362369

> evalm(M4&*(M1 + M3));

Catalan xln(x) (42 + 2 (T42) + + xTsin) + (xx) ((6767 + + GG)) Catalan xln(x) (47 + 2 (G47) + + Gx sin) + (xx) ((8989 + + MM))

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

Векторы (vector); в среде Maple-языка вектор представляется в виде 1D-массива, нумерация строк и столбцов которого производится, начиная с единицы. Вектор определяется либо явно через рассмотренную выше array-функцию, например V:= array(1..n), или посредством процедуры vector, имеющей следующие два простых формата:

vector([X1, X2, ..., Xn]) и vector(n, { |Fn|[X1, X2, ..., Xn]})

где Xk - элементы вектора, n - число его элементов и Fn - функция генерации элементов вектора аналогично случаю определения матриц. По тестирующей же функции type(V, {'vector'(K)| vector}) возвращается значение true, если V-выражение является вектором; при этом, допускается проверка на принадлежность значений его элементов заданной К-области. В противном случае функцией возвращается false-значение. Так как вектор является одним из типов общего понятия массив, то функция type(V, 'array') также возвращает true-значение, если V – вектор. Обратное в общем случае неверно, например, в случае списка. По convert(L, 'vector')-функции возвращается вектор, если L – соответствующая ему списочная структура.

Следует отметить, что в силу специфики организации базовых структур, на которых основываются векторы, массивы, и матрицы, они не вполне однозначно тестируются пакетными процедурой whattype и функцией type, как иллюстрирует фрагмент ниже:

> v:=vector(3,[]): m:=matrix(3,3,[]): a:= array(1..2,1..3,[]): map(whattype, map(eval, [v,m,a]));

[array, array, array]

> map(type, [v,m,a], 'array'), map(type, [v,m,a], 'vector'), map(type, [v,m,a], 'matrix');

[true, true, true], [true, false, false], [false, true, true]

Так, если процедура whattype отождествляет все типы {array, matrix, vector} как обобщающий array-тип, то функция type обеспечивает более дифференцированное тестирование, хотя и в этом случае типы array и matrix ею не различаются. Предыдущий фрагмент хорошо иллюстрирует сказанное.

Основные характеристики матриц и векторов. Прежде всего, среди класса матричных объектов выделим квадратные (square) матрицы, которые будут основным объектом нашего рассмотрения и для работы с которыми Maple-язык располагает довольно широким набором функциональных средств. Поэтому, если не оговаривается противного, то под понятием «матрица» в дальнейшем понимается именно квадратная матрица. Для работы с такого типа матрицами Maple располагает большим набором средств, находящихся в пакетных модулях linalg и LinearAlgebra.

Прежде всего, по функции indices(M) возвращается последовательность 2-элементных списков, определяющих индексное простанство М-матрицы, а по функции entries(M) – последовательность 1-элементных списков, содержащих значения элементов М-матрицы. В случае использования в качестве М-аргумента таблицы функция {indices|entries} возвращает соответственно ее входы и выходы. При этом, следует иметь в виду, что в случае матриц обе функции возвращают последовательности списков, имеющих внутреннее взаимно-однозначное соответствие (при отсутствии оного в их выходных порядках), не управляемых пользователем. Вместе с тем, на основе несложной Pind-процедуры [103], в качестве единственного фактического аргумента использующей выражение типа {array, matrix, table}, можно получать (nxp)-размерность произвольной М-матрицы в виде [n, p]списка, как это иллюстрирует следующий весьма простой фрагмент:

> Fn:= (k, j) -> k^2+3*k*j: M:= matrix(3, 10, Fn): M1:= matrix(3,3,[]): map(evalm, [M, M1]);

18104 16277 223610 284513 543416 634019 467222 815225 285890 649931, M1M1M1132,,, 111 M1M1M1321,,, 222 M1M1M1123,,, 333

> indices(M);

[1, 3], [3, 2], [2, 1], [3, 6], [1, 6], [2, 5], [3, 10], [1, 2], [1, 4], [3,3], [1, 7], [1, 9], [3, 7], [2, 9], [1,5], [2,10],

[2, 4], [1, 1], [3, 4], [2, 7], [1, 8], [3, 8], [2, 6], [2, 2], [1, 10], [2, 8], [3, 1], [3, 5], [2, 3], [3, 9]

> entries(M); ⇒ [10], [27], [10], [63], [19], [34], [99], [7], [13], [36], [22], [28], [72], [58], [16],

[64], [28], [4], [45], [46], [25], [81], [40], [16], [31], [52], [18], [54], [22], [90]

> Pind(M), Pind(M1); ⇒ [3, 10], [3, 3]

> assign(Kr=matrix(3,3,[[a,e*ln(x),c],[d,e*ln(x),f],[e*ln(x),h,k]])), eval(Kr), Mem1(Kr,e*ln(x));

e lna d( )x ee ln lnh ( ( )x x) c k f  , ,3 [1, 2], [2, 2], [3, 1] 

Во фрагменте иллюстрируется определение М-матрицы на основе matrix-функции с использованием Fn-функции генерации элементов матрицы. Представлено применение Pind-процедуры для определения размерности матриц. Процедура Mem1(M, h) возвращает последовательность, первый элемент которой определяет число вхождений в Mматрицу элементов, заданных вторым h-аргументом функции, а последующие определяют списки-координаты искомых элементов. В нашей Библиотеке [103] представлен целый ряд других полезных процедур для работы с матрицами. Можно определять целый ряд других полезных процедур как на основе представленных, так и других функций Maple-языка, что оставляется читателю в качестве весьма полезного практического упражнения. В реальной работе с пакетом это вполне возможные ситуации.

Как таблица, так и общая функция array({<ИФ>}{, <Размерность>}{, <НЗ>}) допускает 3 необязательных ключевых аргумента: ИФ – индексная функция, размерность, кодируемая в виде диапазонов (..) изменения значений индексов, и НЗ – список начальных значений для определения элементов массива. Два последних из них достаточно просты и неоднократно обсуждались при рассмотрении массивов и таблиц. Несколько детальнее остановимся на индексной функции, имеющей для матриц особое значение, определяя общего уровня их классификацию. В общем случае индексная функция определяет правило присваивания значений элементам массива или таблицы, или их обработки. При отсутствия ИФ-аргумента используется стандартный метод индексации элементов массива (матрицы). Индексная функция может определяться пользовательской процедурой, принцип организации которой здесь не рассматривается, но общий подход был указан выше. Для этих целей может использоваться, например, процедура indexfunc пакетного модуля linalg. Язык Maple располагает пятью основными встроенными ИФ с идентификаторами symmetric, antisymmetric, sparse, diagonal и identity для функций table и array. Мы рассмотрим вкратце данные индексные функции.

Индексная symmetric-функция применима в качестве первого фактического аргумента для определения симметричности элементов матрицы/таблицы относительно ее главной диагонали, т.е. для М-матрицы предполагается определяющее соотношение M[k,j] = M[j, k]. В свою, очередь antisymetric-аргумент определяет М-матрицу с соотношением M[k, j] = -M[j, k], следовательно ∀(k)∀(j)(k=j)(M[k, j]=0). Если же при определении такого типа матрицы были заданы ненулевые начальные значения для ее диагональных элементов, то они получают нулевые значения с выводом соответствующей диагностики. Аргумент diagonal определяет диагональную М-матрицу, для которой справедливо соотношение ∀(k)∀(j)(k≠j)(M[k, j]=0). Посредством sparse-аргумента определяется М-матрица, чьи неопределенные входы получают нулевые значения. Например, по вызову array(sparse, 1..n, 1..p) возвращается нулевая матрица (nxp)-размерности. Наконец, по identity-аргументу возвращается единичная матрица, для которой имеет место соотношение ∀(k)∀(j)[(k=j) → (M[k, j] = 1)] & [(k≠j) → (M[k,j] = 0)]. Следующий пример иллюстрирует получение рассмотренных выше пяти типов матриц:

> MS:=array(symmetric, 1..3, 1..3): MS[1, 1]:=10: MS[1, 2]:=17: MS[1, 3]:=39: MS[2, 2]:=64:

MS[2, 3]:=59: MS[3, 3]:=99: MD:=array(diagonal, 1..3, 1..3): MD[1, 1]:=2: MD[2, 2]:=10:

MD[3, 3]:=32: MAS:= array(antisymmetric, 1..3, 1..3): MAS[1, 2]:=10: MAS[1, 3]:=32:

MAS[2, 3]:=52: MI:= array(identity, 1..3, 1..3): MSp:=array(sparse, 1..3, 1..3): map(evalm,

[MS, MAS, MD, MI, MSp]);

39 59 99 ,  -10-320 -5210 0 5232 0 ,  2 00 1000 3200  ,  1 00 100 100 , 0 00 000 000      10 17 6417 5939

Так как вектор представляет собой (1xn)-матрицу, то к нему применим и целый ряд сугубо матричных функций, например evalm-функция, позволяющая возвращать вычисленные векторные выражения в векторной (списочной) нотации. Более того, все имеющее силу относительно матриц в полной мере (с поправкой на размерность) относится и к векторам. В частности, это относится и к (&*)-оператору матрично/векторного произведения. Длину V-вектора, подобно случаю матриц, можно вычислять, в частности, посредством конструкции вида nops([{indices|entries}(V)]); при этом, нулевое значение возвращается, если элементам V-вектора не присваивалось значений. Следующий простой фрагмент иллюстрирует некоторые способы определения, тестирования и вычисления простых векторных выражений в среде Maple-языка:

> V1:= array(1..9): V2:= array(1..6, [64, 59, 39, 10, 17, 44]): V3:= vector(5, [42, 47, 67, 89, 96]): V4:= vector(5, [64, 59, 39, 17, 10]): Fn:= k -> 3*k^2 + 10*k + 99: V5:= vector(5, Fn):

V6:= vector(6, [V, G, S, Art, Kr, Ar]): map(type, [V1, V2, V3, V4, V5, V6], 'vector');

[true, true, true, true, true, true]

> map(evalm, {V5, V6}); ⇒ {[112, 131, 156, 187, 224], [V, G, S, Art, Kr, Ar]}

> map(nops, [[indices(V6)], [entries(V6)]]); ⇒ [6, 6]

> V:=vector(6, []): map(nops, [[indices(V)], [entries(V)]]); ⇒ [0, 0]

> [type(V4, 'vector'(integer)), type(V6, 'vector'(symbol))]; ⇒ [true, true]

> Z:= vector([W, G, S]): M:= array(1..3, 1..3, [[1, 2, 3], [4, 5, 6], [7, 8, 10]]): evalm(M&*Z);

[W + 2 G + 3 S, 4 W + 5 G + 6 S, 7 W + 8 G + 10 S]

Для обеспечения работы с матричными и векторными выражениями Maple-язык располагает довольно развитым набором функциональных средств, поддерживаемых, в первую очередь, пакетными модулями linalg и LinearAlgebra. Данные модули содержат определения 114 и 118 процедур соответственно (в зависимости от релиза пакета; пример приведен для Maple 10), обеспечивающих расширенные средства линейной алгебры по работе с матричными и векторными выражениями. Учитывая обилие указанных средств и направленность данной книги, мы не будем акцентировать на них внимания, отсылая к нашей книге [12] либо к ее авторскому оригинал-макету, который можно загрузить с университетского Web-сайта www.grsu.by/cgi-bin/lib/lib.cgi?menu=links&path=sites. В них рассмотрены средства пакета, составляющие определенную базу для обеспечения работы с матрично/векторными выражениями в рамках классической линейной алгебры. Лишь кратко определим способы доступа к таким модульным средствам пакета.

Ряд средств линейной алгебры имеют классический формат вызова вида Id(<аргументы>), тогда как основная масса таких средств определяется пакетным модулем linalg. Поэтому первый вызов любого из них должен предваряться предложением одного из следующих трех форматов, а именно: with(linalg), with(linalg, <Процедура_1>, <Процедура_2>, <Процедура_3>, ...) или linalg[<Процедура>](<аргументы>). Исходя из обстоятельств, пользователь будет производить вызов процедур linalg-модуля необходимым ему способом. Первый формат обеспечивает доступ сразу ко всем процедурам модуля, но их загрузка требует дополнительной памяти в рабочей области пакета, второй формат активирует определения конкретных процедур, используемых в текущем сеансе, и третий формат определяет разовый вызов конкретной процедуры на конкретных фактических аргументах. Последний формат часто используется в пользовательских процедурах. Вызов процедуры packages() возвращает список пакетных модулей, загруженных в текущий сеанс. Вышесказанное иллюстрирует следующий простой пример:

> restart; packages(), map(type, [col, det], 'procedure'); ⇒ [], [false, false] > with(linalg, col, det), packages(), map(type, [col, det], 'procedure');

[col, det], [linalg], [true, true]

Из приведенного примера следует, что загрузка даже отдельных процедур модуля идентифицируется процедурой packages как загрузка модуля в целом.

С учетом сказанного, средства модуля linalg не представляют каких-либо затруднений при использовании их знакомым с основами линейной алгебры читателем, а приведенные здесь и в прилож. 1 [12] примеры и дополнительные замечания вполне достаточно иллюстрируют средства матричной алгебры, поддерживаемые Maple-языком. Наряду с наиболее общими linalg-модуль располагает целым рядом других, более специальных, матричных средств, включая средства создания специального типа матриц (Вандермонда, Теплица, Сильвестра и др.), в полном же объеме со средствами матричной алгебры, обеспечиваемыми Maple-языком, рекомендуется ознакомиться по книгам [10-14,59,80,86,90].

Базовые структуры данных модуля LinearAlgebra . Одну из самых больших особенностей Maple составляет его модуль LinearAlgebra, определяющий новые стандарты эффективности, надежности, полезных свойств и точности для вычислительной линейной алгебры. Данная задача была решена путем интегрирования в пакет современных программ линейной алгебры фирмы NAG (Numerical Algorithm Group) через внешний механизм вызовов [13,14,39]. Это позволяет использовать мощные вычислительные алгоритмы NAG для решения задач линейной алгебры с высокими точностью и производительностью. Однако, прежде, чем переходить к характеристике средств LinearAlgebraмодуля, кратко остановимся на различиях между ним и рассмотренным выше linalg-модулем линейной алгебры.

Если в основе векторно-матричных объектов, с которыми оперирует модуль linalg, лежит структура данных array-типа, то основу объектов, обрабатываемых средствами модуля LinearAlgebra, составляет так называемая rtable-структура данных, генерируемая одноименной встроенной функцией. Данная функция и генерируемые ею rtable-объекты детально были нами рассмотрены в [13,14,39]; ряд новых средств по работе с такого типа объектами как отдельно, так и в совокупности с array-объектами представлен нами в книгах [39,41,42,45,46,103], а также в нашей Библиотеке [103,109].

Визуализация rtable-объекта (Array, Matrix, Vector) определяется его размером. Если размер его меньше или равен значению, определенному предопределенной rtablesize-переменной процедуры interface (по умолчанию rtablesize=10), то объект визуализируется полностью. В противном случае он заменяется специальным шаблоном. Установка опции rtablesize=0 определяет вывод любого rtable-объекта в виде шаблона, тогда как установка rtablesize=infinity определяет вывод rtable-объекта полностью безотносительно его размера, как иллюстрирует следующий фрагмент:

> interface(rtablesize=2); Kr:= rtable(1..3, [89, 11, 99]); restart: Kr:=rtable(1..3, [89, 11, 99]);

Kr :=  1..3 1-D Array Data Type: anything Storage: rectangular Order: Fortran_order 

Kr := [89, 11, 99]

> interface(rtablesize = 6); Kr:= rtable(1..3, 1..3, [[42, 47, 67], [64, 59, 39], [44, 10, 17]]);

42 47 67

Kr := 6444 5910 3917  

Из фрагмента нетрудно заметить, что шаблон rtable-объекта представляет собой стандартный описатель объекта (размерность, тип и основные характеристики). В случае больших размеров массивов данное средство оказывается весьма удобным. При этом, следует иметь в виду следующее важное обстоятельство. Если размер rtable-объекта больше определяемого rtablesize-переменной, то выводится его шаблон даже в случае наличия рекурсивности в его определении, в противном случае сразу же идентифицируется аварийная ситуация, требующая перезагрузки пакета. Причиной этого является переполнение системного стэка. Впрочем, работа пакетного стэка и так имеет много нареканий.

Для работы с rtable-объектами пакет располагает рядом полезных функций и процедур, детально рассмотренных в [45,46]. Данные средства применимы к любому rtable-объекту (Array, Matrix, Vector), однако каждый из трех типов объектов располагает и собственными аналогичными средствами, кратко рассматриваемыми ниже. Для вывода rtable-объектов можно использовать и форматирующие функции printf-группы: printf, fprintf, sprintf, nprintf, с опциями которых для этого случая можно ознакомиться по справке пакета. Аналогично этому к любому rtable-объекту применимы и функции scanf-группы (scanf, fscanf, sscanf) для выполнения синтаксического анализа объектов.

Объекты rtable-типа . На основе упомянутой rtable-функции определяются три базовых объекта LinearAlgebra-модуля: Array, Matrix и Vector. Непосредственно посредством rtable-функции в среде пакета можно определять любой из указанных трех объектов. После их создания с ними можно работать в рамках алгебры, определяемой операциями, довольно детально рассмотренными в книгах [39,41,42,45,46,103]. Приведем простой фрагмент, иллюстрирующий использование операций rtable-алгебры пакета.

> A:=rtable(1..4, 1..4, random(4..11, 0.95), subtype=Array, storage=sparse, datatype=integer):

M:= rtable(1..4, 1..4, random(42..99, 0.58), subtype=Matrix, storage=rectangular): C:=rtable(1..4, 1..4): V:= rtable(1..4, random(42..99, 0.64), subtype=Vector[column], storage=rectangular): A, M, V, C;

10 869 106 55 107 56 108 56 , 69 95460 73 9500 54 536885 55 49760 ,   45 477284 ,  0 000 0 000 0 000 0 000 

> Vr:= rtable(1..4, random(47..99, 0.99), subtype = Vector[row], storage = sparse, datatype = integer); ⇒ Vr := [65, 56, 57, 90]

> Vr.M^(-2) + 6*Vr;

208520698 897237 , 787693955 1794474 , 13386043721 34095006 , 164952480446 301172553 

> A^2 + 10*A + 99, M^2 + 17*M + 95, (Vr.M + 17*Vr).(10*M + 17);

299 243195270

[3

195

299

174

174

15128

218

174

195

299

80 27,

174

243 , 14517 10416206155035 11408 1662550359913 17113 24383120175406

299

195

251067 36562329 26471066, , ]

8531

12411,

17632

4123

С учетом сказанного, примеры фрагмента особых пояснений не требуют. В этой связи имеет смысл лишь вкратце пояснить различие между табличной организацией собственно Maple-среды (базируется на table-функции) и NAG-организацией (базируется на функции rtable). В первом случае массивы и таблицы базируются на внутренних хэш-таблицах пакета, тогда как во втором случае используется формат импортированного NAG-модуля линейной алгебры. В этом случае для каждого измерения rtable-объекта используется по одному вектору индексов и отдельный вектор отводится под значения элементов массива. На основе такого представления формируются такие структуры данных как Array, Matrix, Vector[row] и Vector[column]. Более того, следует иметь в виду, что одноименные рассмотренным структуры array, matrix и vector, начинающиеся со строчных букв (исключение составляют пассивные функции), относятся к средствам собственно Maple-языка и их обработка производится иными средствами, детально рассмотренными в цитируемых выше книгах. При этом, следует отметить, что функции на основе rtable-функции принципиально отличаются от одноименных функций array, matrix и vector, как отмечалось выше.

Базовые объекты модуля LinearAlgebra . В качестве таких объектов выступают Array, Matrix, Vector[row] и Vector[column], кратко рассмотренные выше в связи с rtable-функцией, на основе которой они формируются. Между тем, пользователь имеет возможность непосредственно создавать указанные объекты на основе встроенных функции Array и процедур Matrix или Vector, кратко рассматриваемых ниже.

По функции Array(ИФ, D, НЗ, <Опции>) создается массив с заданными характеристиками, определяемыми ее фактическими аргументами. Каждый из аргументов функции не является обязательным; если же вызов функции Array определен без фактических аргументов, то возвращается пустой 0-мерный массив. Смысл и форматы кодирования аргументов функции (ИФ - индексирующая функция, D - размерность, НЗ - начальные условия и <Опции>) довольно прозрачны и особых пояснений не требуют. Поэтому на данном вопросе детальнее останавливаться не будем, а приведем несколько примеров на прямое определение массивов Array-типа.

> A:= Array(1..6, [42, 47, 67, 62, 89, 96], datatype=integer, readonly): Ao:=Array(): A, Ao;

[42, 47, 67, 62, 89, 96], Array({}, datatype = anything, storage = rectangular, order = Fortran_order)

> A[4]:= 2006;

Error, cannot assign to a read-only Array

> ArrayOptions(A);

datatype = integer, storage = rectangular, order = Fortran_order, readonly

> B:=Array(symmetric, 1..3, 1..3, datatype=integer): B[1, 1]:=42: B[2, 2]:=47: B[3, 3]:=67: B[1, 2]:= 64: B[1, 3]:=59: B[2, 3]:=39: B, ArrayIndFns(B), ArrayOptions(B);

42 6459 64 4739 59 3967 , symmetric, datatype = integer, storage = triangularupper,

order = Fortran_order

> op(1, B), ArrayOptions(B, order=C_order), ArrayOptions(B);

symmetric, datatype = integer, storage = triangularupper , order = C_order

> ArrayDims(B), ArrayNumDims(B); ⇒ 1 .. 3, 1 .. 3, 2

> ArrayElems(B);

{(1, 1) = 42, (1, 2) = 64, (1, 3) = 47, (2, 2) = 59, (2, 3) = 39, (3, 3) = 67}

> ArrayNumElems(B, NonZeroStored), ArrayNumElems(B, All); ⇒ 6, 9

Относительно сугубо Maple-объектов NAG-объекты характеризуются двумя важными чертами, а именно: (1) ссылка на их идентификатор возвращает непосредственно сам объект, не требуя таких функций как evalm, и (2) объекты создаются всегда с определенными элементами, по меньшей мере нулевыми, если не было определено противного. Второе обстоятельство весьма упрощает ряд процедур с такими объектами, не требуя их предварительного обнуления. Обусловлено это тем, что если Maple-объекты изначально ориентированы на символьные вычисления, то NAG-объекты на числовые.

Для работы с Array-объектами Maple-язык располагает рядом полезных функций, детально рассмотренных в наших книгах [13,14,29,30,33]. Примеры предыдущего фрагмента иллюстрируют создание посредством Array-функции одномерного А-массива и двумерного В-массива, а также применение к ним ряда функций работы с массивами, которые ввиду их простоты не требуют дополнительных пояснений.

Вызов процедуры Matrix(n, m, НЗ, <Опции>) возвращает матричную структуру данных (матрицу), являющуюся одной из базовых структур, с которыми работают функциональные средства LinearAlgebra-модуля. Первые два формальных аргумента процедуры определяют число строк и столбцов матрицы соответственно; при этом, фактические значения для аргументов могут кодироваться или целыми неотрицательными числами, или диапазонами вида 1 .. h (h – целое неотрицательное). Третий аргумент определяет начальные значения, тогда как опции - дополнительные характеристики создаваемого матричного объекта. Все аргументы процедуры необязательны и при их отсутствии возвращается матрица (0х0)-размерности. В общем же случае при создании матричного объекта вызов Matrix-процедуры должен содержать достаточное количество информации о его структуре и элементах. Сказанное по начальным значениям относительно rtable-функции в полной мере переносится и на третий аргумент Matrix-процедуры. Среди допустимых опций (четвертый аргумент) многие аналогичны общему случаю rtable-функции, рассмотренной выше. Однако, среди них имеются и специфические для матричной структуры. Детальнее с описанием опций функции можно ознакомиться по справке пакета. Приведем примеры на создание некоторых простых матричных объектов и их тестирование.

> M1:=Matrix(3,[[42,47,67],[64,59,39],[89,96,62]],readonly): M2:=Matrix([[a,b],[c,d]]): M1,M2;

426489 475996 673962, ac bd

> M1[3, 3]:= 47;

Error, cannot assign to a read-only Matrix

> M3:= Matrix(3, (j, k) -> 4*j^2+11*k^2-89*j-96*k+99*j*k, datatype=integer[2]): M4:=

Matrix(1..3,1..3,rand(42..99,0.58)): M5:= <<42,47,67>| <64,59,39>|<10,17,44>>: M3, M4, M5;

-49 86 243 , 44

-71-19 215-35 47123 617167 5898 538699, 424767 645939 101744

> map(type, [M1, M2, M2, M5], 'Matrix'); ⇒ [true, true, true, true]

> M6:= Matrix(2, {(1, 1)=64, (1, 2)=59, (2, 1)=10, (2, 2)=17}): type(M5,'matrix'), whattype(M5), type(M6, 'Array'), type(M6, 'Matrix'); ⇒ false, Matrix, false, true

Фрагмент представляет различные способы определения Matrix-объектов, принцип которых легко усматривается из самих примеров. Из фрагмента также следует, что в компактном виде Matrix-объект можно определять конструкцией следующего вида:

M := <M11, M21, M31>|<M12, M22, M32>|<M13, M23, M33>

недостатком которой является невозможность указания для матрицы в точке определения других ее характеристик. Более того, последний пример фрагмента иллюстрирует тот факт, что type-функция не распознает Matrix-объект в качестве Maple-матрицы, тогда как тестирующая whattype-процедура определяет его как Matrix-объект. Детальнее с описанием и применением функции Matrix можно ознакомиться в книгах [13,14,42] и в справке по пакету. С учетом сказанного Matrix-объекты (NAG-матрицы) довольно прозрачны, но за более подробной информацией по их определению можно обращаться к справке по пакету (например, оперативно по конструкции ?Matrix).

Наконец, по функции Vector[T](n,НЗ, <Опции>) возвращается векторная структура данных (вектор), являющаяся одной из основных структур, с которыми работают функциональные средства LinearAlgebra-модуля пакета. Индекс Т определяет сам тип вектора (column – столбец, row – строка, по умолчанию полагается column). Первый формальный аргумент функции определяет число элементов вектора; при этом, фактические значения для аргумента могут кодироваться или целым неотрицательным числом, либо диапазоном вида 1..h. Второй аргумент определяет начальные значения, тогда как опции – дополнительные характеристики создаваемого векторного объекта. Все аргументы функции необязательны и при их отсутствии возвращается вектор 0-размерности, т.е. элемент 0-мерного векторного пространства. В общем же случае при создании векторного объекта вызов Vector-функции должен содержать достаточное количество информации о его структуре и элементах. Сказанное выше по начальным значениям относительно rtable-функции в полной мере переносится и на второй аргумент Vector-функции. Среди допустимых опций (третий аргумент) многие аналогичны общему случаю rtable-функции, рассмотренной выше. Однако допускаемые ими значения имеют векторную специфику. Детальнее с описанием опций Vector-функции можно познакомиться в справке по пакету. Приведем простые примеры на создание векторных объектов.

> V1:= Vector(1..4, [42, 47, 67, 89]): V2:= Vector[row](4, {1=64, 2=59, 3=39, 4=17}): V3:= Vector(1..4): V1, V2, V3;

 4247 6789 , [64 59 39 17, , , ],   00 00 

 > VectorOptions(V2); shape = [], datatype = anything, orientation = row, storage = rectangular, order = Fortran_order > V4:= <Sv, Ar, Art, Kr>: V5:= <Sv|Ar|Art|Kr>: V4, V5, VectorOptions(V4, readonly);

ArtKr Ar Sv  , [Sv Ar Art Kr, , , ], false

> V6:= Vector[row](6, rand(42..99)): V7:=Vector(): V6, type(V6, vector), whattype(V6), V7;

[57, 89, 80, 52, 48, 71], false, Vector[row], []

Фрагмент представляет различные способы определения Vector-объектов, принцип которых усматривается из самих примеров. Из фрагмента также видно, что в компактной форме объект Vector-типа можно определять конструкциями следующего вида:

V := <V1, V2, V3, …, Vn> или V := <V1|V2|V3| … |Vn>

недостатком которых является невозможность задания для вектора в точке его определения других характеристик. При этом, первый формат определяет вектор-столбец, тогда как второй – вектор-строку. Более того, последний пример фрагмента иллюстрирует тот факт, что type-функция не распознает Vector-объект в качестве Maple-вектора, тогда как тестирующая whattype-процедура определяет его как Vector-объект. С учетом сказанного Vector-типа объекты (NAG-векторы) довольно прозрачны и за более детальной информацией по их определению можно обращаться к справке (например, оперативно по конструкции ?Vector).

Как уже отмечалось выше, между Maple-объектами (array, vector и matrix) и NAG-объектами (Array, Vector и Matrix) имеются принципиальные различия. Более того, классификация вторых относительно тестирующих функции type и процедуры whattype выгодно отличается однозначностью, тогда как первые распознаются whattype-процедурой как объекты array-типа. В приведенном ниже фрагменте этот момент иллюстрируется весьма наглядно:

> a:=array(1..2, 1..2, [[42,47], [64,59]]): A:=Array(1..3, 1..3): v:=vector([1,2,3]):

V:=Vector([1,2,3]): m:=matrix([[G, S, Vic], [47, 67, 42]]): M:=Matrix([[G, S, Vic], [47, 67, 42]]):

> type(a, 'array'), type(a, 'matrix'), type(v, 'vector'), type(v, 'array'), type(m, 'matrix'), type(m,'array'); ⇒ true, true, true, true, true, true

> type(A, 'Array'), type(A, 'Matrix'), type(V, 'Vector'), type(V, 'Array'), type(M, 'Matrix'), type(M, 'Array'); ⇒ true, false, true, false, true, false

> map(whattype, map(eval, [a, v, m])), map(whattype, [A, V, M]);

[array, array, array], [Array, Vector[column], Matrix]

> convert(a, 'listlist'), convert(A, 'listlist'); ⇒ [[42, 47], [64, 59]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]] > a[1]:= [x, y, z];

Error, array defined with 2 indices, used with 1 indices

> A[1]:= [x, y, z]; ⇒ A[1] := [x, y, z]

> convert(A, 'listlist'); ⇒ [[[x, y, z], [x, y, z], [x, y, z]], [0, 0, 0], [0, 0, 0]]

> Ar:=Array(1..2, 1..2, 1..4, [[[64, 59, 39, 10], [47, 67, 42, 6]], [[c2, b2, a2, 17], [c3, b3, a3, 6]]]): > convert(Ar, 'listlist'); ⇒ [[[64, 59, 39, 10], [47, 67, 42, 6]], [[c2, b2, a2, 17], [c3, b3, a3, 6]]] > Ar[1, 2]:= AVZ; ⇒ Ar[1, 2] := AVZ

> convert(Ar, 'listlist'); ⇒ [[[64,59,39,10], [AVZ,AVZ,AVZ,AVZ]], [[c2,b2,a2,17], [c3,b3,a3,6]]]

> A1:= Array(1..3, 1..3, [[a, b, с], [x, y, z], [42, 47, 6]]); ⇒ A1 :=

42ax 47by ñ6z

> A1[2]:= [Grodno, Tallinn]: A1;

[Grodno Tallinn42a , ] [Grodno Tallinn47b , ] [Grodno Tallinnñ 6, ]

В целом ряде случаев точная идентификация rtable-объектов тестирующими средствами пакета играет весьма существенную роль. Еще на одном моменте данного фрагмента имеет смысл обратить внимание, а именно. Если 2-мерный Maple-массив (array) не допускает возможности замены своих строк путем присвоения, инициируя ошибочную ситуацию, то массив NAG (Array) такую операцию допускает, однако присваиваемое значение дублируется по числу элементов строки. Соответствующим образом это обобщается и на n-мерные массивы, как показано выше. В ряде случаев это может представить практический интерес при программировании ряда приложений в среде пакета.

rtabobj := proc()

local a b c k j t A M V x y z, , , , , , , , , , , ; assign(a = map(convert, {anames '( rtable')}, 'string'), b = { }, c = { }, x = [ ], y = [ ], z = [ ]);

seq `if`( (search(k, "RTABLE_SAVE/" ' ', t ) and t = 1, assign(' 'c = {k, op( )c }), assign('b' = {k, op(b)})), k = a); for k in b do

if type(`` k || , 'Array') then A := [ ]k ;

for j in c do

if convert(`` || k, 'listlist') = convert(`` || j, 'listlist') then

A := [op(A), ]j end if

end do; x := [op( )x , A]

elif type(`` || k, 'Matrix') then M := [ ]k ; for j in c do

if convert(`` k || , 'listlist') = convert(`` j || , 'listlist') then M := [op(M), ]j

end if

end do; y := [op( )y , M]

else

V := [ ]k ; for j in c do

if convert(`` k || , 'listlist') = convert(`` j || , 'listlist') then

V := [op(V), ]j end if

end do; z := [op(z), V]

end if

end do; x y z, ,

end proc

> rtabobj();

[["A"], ["A1", "RTABLE_SAVE/12988492", "RTABLE_SAVE/12468956"]],

[["M", "RTABLE_SAVE/15041652", "RTABLE_SAVE/15049896"], ["M1",

"RTABLE_SAVE/15041652", "RTABLE_SAVE/15049896"]],

[["V", "RTABLE_SAVE/3384632"], ["V1", "RTABLE_SAVE/14862784", "RTABLE_SAVE/15017212", "RTABLE_SAVE/15026360"]]

Вместе с тем, Maple-объекты существенно проще NAG-объектов и несложно конвертируются во вторые. В наших книгах [29,33,42,43,103] и приложенной к ним Библиотеке представлены дополнительные средства конвертации Maple-объектов в NAG-объекты, и наоборот. Наряду с ними, представлен ряд других средств по работе с rtable-объектами, которые существенно расширяют стандартные средства пакета. Эти средства оказываются достаточно полезными при продвинутом программировании разнообразных задач, имеющих дело с rtable-объектами, обеспечивая Maple-программиста целым рядом дополнительных возможностей. Так, например, вызов представленной выше процедуры rtabobj() возвращает трехэлементную последовательность вложенных (в общем случае) списков, где элементы данной последовательности представляют информацию о rtable-объектах в разрезах типов Array, Matrix и Vector соответственно, активных в текущем сеансе. При этом, каждый подсписок первым элементом в строчном формате определяет имя rtable-объекта соответствующего типа, тогда как остальные его элементы определяют в строчном формате соответствующие ему идентификационные номера вида «RTABLE_SAVE/nnnnnnnn». Более детальный анализ данной информации позволяет, например, прослеживать историю работы с rtable-объектами текущего сеанса [42,103].

Процедура MwsRtb(F) анализирует mws-файл F на наличие в нем rtable-объектов [103].

MwsRtb := proc(F::{string, symbol, name}) local a b c d k p h t, , , , , , , ; if not type( ,F 'file') or Ftype(F) ≠ ".mws"then

ERROR("%1 is not a mws-datafile or does not exist", F) else assign(a = RS([`\n` = ``], readbytes(F, 'TEXT', ∞))), close(F); if not search( ,a "{RTABLE_HANDLES" ' ', t ) then RETURN(

WARNING("rtable-objects do not exist in datafile <%1>", F)) end if;

assign(' 'a = a[t + 1 .. -1], c = "{RTABLE "), search( ,a "}{" ' ', t ), assign(b = map(SD, SLD(a[15 .. t − 2], " ")), 'a' = a[t + 1 .. -1]),

`if`(1 < nargs and type(args 2[ ], 'symbol'), assign(args 2[ ] = b), NULL)

end if;

assign(p = SLD(a c, ), d = map2(cat, "RTABLE_SAVE/", b), h = MkDir cat(( [libname ][1][1 .. 2], "\rtbobj")));

for k to nops(p) do

assign(' 'a = cat(h, "\_", b k[ ], ".m")), writeline(a, cat(p k[ ][1 .. 4], "

", p k[ ][5 .. -2])), close(a); read a

end do;

d, WARNING("rtable-objects of datafile <%1> are active in the current session\

and have been saved in directory <%2>; their names are %3", F h, , map2(cat, "_", ,b ".m" ))

end proc

> MwsRtb("C:/Academy\\Examples\\MPL9MWS\\IdR.mws", 'h'); h;

Warning, rtable-objects of datafile <C:/Academy\Examples\MPL9MWS\IdR.mws> are

active in a current session and have been saved in directory <c:\program files\ maple 9\rtbobj>; their names are [_5290620.m, _5603484.m, _5188712.m]

["RTABLE_SAVE/5290620", "RTABLE_SAVE/5603484", "RTABLE_SAVE/5188712"] [5290620, 5603484, 5188712]

При наличии таких объектов возвращаются их вызовы в строчном формате, иначе возвращается NULL-значение. Через необязательный второй аргумент возвращаются идентификационные номера (rtable-индексы) rtable-объектов, находящихся в файле F.

Весьма детальный обзор функциональных средств модуля LinearAlgebra, его базовые структуры данных, средства их создания и алгебра над ними детально рассмотрены в вышеупомянутых книгах [13,14,29,30,33]. Пакетный модуль LinearAlgebra предназначен как для интерактиного режима использования, так и для эффективного программирования в Maple. Для этого пакет прикладных программ линейной алгебры фирмы NAG был имплантирован в среду пакета Maple как программный модуль. Данная организация позволяет обращаться к его средствам следующими двумя способами, а именно:

(1) как к стандартному модулю пакета по конструкции формата: LinearAlgebra[Функция](Аргументы) (2) как к программному модулю по конструкции формата:

LinearAlgebra:- Функция(Аргументы)

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

Начиная с 6-го релиза, пакет Maple предоставляет два альтернативных выбора при решении задач линейной алгебры, базирующихся соответственно на модулях LinearAlgebra и linalg. Средства первого модуля и его концепция были довольно детально рассмотрены в наших книгах [8-14], тогда как второго в книгах [29-33]. Отметим здесь только наиболее характерные отличительные черты модуля LinearAlgebra. Прежде всего, модуль LinearAlgebra представляет собой большой набор процедур линейной алгебры, покрывающих почти все функциональные возможности пакета linalg. Вместе с тем, он имеет значительно более четкие структуры данных, располагает дополнительными средствами для создания специальных типов матриц, и улучшенными возможностями для решения матричных задач. Его преимущества особенно наглядны при вычислениях с большими числовыми матрицами, элементами которых являются значения float-типа (как данные пакетной float-арифметики, так и данные машинной float-арифметики). В качестве иллюстрации представим ряд примеров использования средств модуля LinearAlgebra для решения некоторых массовых задач линейной алгебры:

> alias(LA = LinearAlgebra): A:= <<64, 59, 39>|<42, 47, 67>|<38, 62, 95>>: B:= <10, 17, 99>: > LA[Transpose](LA[LinearSolve](A, B)), LA[Transpose](LA[LeastSquares](A, B));

-33081855 , 69211855, -67 , -33081855 , 69211855, -67 

> LA:- Determinant(A);

Error, `LA` does not evaluate to a module

> LA[Determinant](A); ⇒ -33390

> M:= rtable([[64, 59, 39], [42, 47, 67], [43, 10, 17]], subtype = Matrix, readonly = true);

644243 594710 671739

M :=

> LA[Determinant](M), LA[Rank](M), LA[Trace](M); ⇒ 73670, 3, 128 > evalf(LA[Eigenvalues](M, output = 'Vector[row]'));

[131.7149459, -1.85747297 + 23.57676174 I, -1.85747297 - 23.57676174 I]

> evalf(LA[MatrixInverse](M, method='LU'), 6); ⇒

0.00175105-0.02173200.0294150 -0.00799511-0.008320890.0257500 0.00719424-0.03597120.0287770 

> evalf(LA[QRDecomposition](evalf(M), output = 'NAG'), 6);

0.2766779999999999800.283266000000000018-87.8008999999999986 -0.664429999999999965-70.3864999999999981-28.9090999999999988 -26.8831999999999988-68.803399999999996429.0239000000000011 ,

1.728920000000000011.387480000000000040. 

> LA[FrobeniusForm](M); LA[CharacteristicPolynomial](%, h);

010 001 73670128-70

h3 − 128 h2 + 70 h − 73670

> evalf(LA[SingularValues](evalf(M), output = 'list'), 6); ⇒ [136.209, 30.5125, 17.7259]

> evalf(LA[Eigenvectors](evalf(M), output = 'vectors'), 8);

[0.720627899999999988 + 0. I ,

0.278720599999999984 + 0.381360960000000027 I ,

0.278720599999999984 − 0.381360960000000027 I]

[0.613184240000000047 + 0. I , -0.671878590000000053 + 0. I ,

-0.671878590000000053 + 0. I]

[0.323574589999999995 + 0. I ,

0.315224249999999984 − 0.475490770000000006 I ,

0.315224249999999984 + 0.475490770000000006 I]

> LinSolve:=(P::package, A::{Matrix, matrix}, B::{Vector, vector}) -> op([assign('K'=with(P)),

`if`(`if`(P = linalg, det, Det)(A)<>0, `if`(P = LinearAlgebra, LinearSolve, linsolve)(A, B),

ERROR("Matrix is singular"))]): LinSolve(LinearAlgebra, A, B); # (1)

LinearSolve  64 5939 42 4767 38 9562  ,   10 1799   

> a:= matrix([[64, 59, 39], [42, 47, 67], [38, 62, 95]]): b:= vector([10, 17, 99]): # (2)

> alias(la = linalg): evalf(LinSolve(linalg, a, b)); ⇒ [-4.75624439, 5.94860737, -0.937646002]

Прежде всего, следует обратить внимание на одно важное обстоятельство. С целью устранения неудобства, связанного с многократным использованием довольно длинного имени модуля LinearAlgebra, ему был присвоен алиас "LA", однако этот довольно удобный подход выявил ряд его особенностей. Прежде всего, алиас нельзя использовать в конструкциях вида LA:- Функция, как это хорошо иллюстрирует третий пример фрагмента. В этом случае следует использовать конструкцию LA[Функция]. Общей рекомендацией является определение алиаса для имени модуля вне тела процедуры/функции, в таком случае он может использоваться наравне с основным именем. Второй пример фрагмента иллюстрирует решение системы линейных уравнений посредством 2-х процедур LinearSolve и LeastSquares модуля. Посредством Transpose-процедуры результат решения системы линейных уравнений A.X = B по-лучаем в виде вектора-строки. Последующие примеры фрагмента иллюстрируют применение различных процедур пакетного модуля LinearAlgebra. Исключение составляют последние примеры 1 и 2, иллюстрирующие принципиальное отличие модуля linalg от LinearAlgebra относительно их использования внутри определений функций/процедур. В примере (2) показано, что использование вызова with(linalg) внутри тела функции делает доступными процедуры мо- дуля linalg как в области определения самой функции, так и вне ее. Тогда как согласно примера (1) аналогичный подход, но на основе модуля LinearAlgebra не работает, что в определенной мере сужает выразительные возможности программирования с использованием его функциональных средств. Имеется ряд других различий пакетных модулей linalg и LinearAlgebra, обусловленных проблемами полной интеграции второго в среду пакета, однако мы не будем здесь на них останавливаться. Заинтересованный же читатель отсылается к нашим книгам [13-14,29-33,39,41-43,45,46,103].

С учетом сказанного рассмотренные средства линейной алгебры и их базовые структуры данных не представляют каких-либо затруднений при использовании их знакомым с основами линейной алгебры читателем. В свете сказанного следует иметь в виду, что нами были представлены наиболее употребительные форматы матрично-векторных средств Maple-языка с определенными акцентами скорее на особенностях их реализации и выполнения, чем на их математической сущности, хорошо известной имеющим опыт в данном разделе математики. Поэтому за деталями их описания необходимо обращаться к справочной системе пакета либо к цитированной выше литературе. Однако, многие вопросы снимаются при практической апробации рассмотренных средств. В настоящее время имеется целый ряд внешних модулей пакета (часть из них поставляется по выбору), весьма существенно расширяющих рассмотренные базовые средства матричновекторных операций Maple-языка. Эти средства постоянно расширяются. На базе рассмотренных средств Maple-языка пользователь имеет возможность программировать и собственные, недостающие для решения его матрично-векторных задач, средства. Пакет постоянно, расширяя свои приложения, между тем, не в состоянии в должной мере учесть все потребности, поэтому его программная среда и предоставляет пользователям возможность расширять его новыми средствами под ваши специфические нужды. Типичным примером такого подхода и является наша Библиотека программных средств [41,42,103,109]. Ниже вопросы решения задач линейной алгебры в среде Maple (учитывая специфику настоящей книги) не рассматриваются. Заинтересованный читатель отсылается к книгам [8-14,78,84,86,88,55,59-62], а также к [91] с адресом сайта, с которого можно бесплатно загрузить некоторые книги, и к [109] с нашей Библиотекой.

Псевдослучайные числа. Еще с одним видом значений - псевдослучайных пользователь имеет возможность работать на основе встроенного генератора псевдослучайных чисел (ГПСЧ), активизируемого по rand-процедуре, имеющей три формата кодирования. По вызову rand() возвращается псевдослучайное неотрицательное integer-число (ПСЧ) длиной в 12 цифр, тогда как по вызову rand({n|n..p}) (n, p – целочисленные значения и n ≤ p) возвращается равномерно распределенное на интервале соответственно [0 .. n] и [n .. p] целое ПСЧ. Для установки на-чального значения для ГПСЧ используется предопределенная _seed-переменная пакета, имеющая значение 427419669081, которое в любой момент может быть переопределено пользователем, например: _seed:= 2006.

Для этих же целей, но с более широкими возможностями, используется и randomize-процедура, кодируемая в одном из допустимых форматов вида: randomize({|n}), где n > 0 – целочисленное выражение, значение которого и присваивается _seed-переменной. Если используется формат randomize() вызова процедуры, то для _seed-переменной устанавливается значение, базирующееся на текущем значении системного таймера. Так как вызов randomize() возвращает базовое значение для ГПСЧ на основе текущего значения таймера, то таким способом можно достаточно эффективно генерировать различные последовательности ПСЧ. Сохраняя необходимые значения _seed-переменной, можно повторять процесс вычислений с псевдослучайными числами, что особенно важно в случае необходимости проведения повторных вычислений.

Вызов rand может инициировать вывод самого тела соответствующей ей процедуры, рекомендуемый подавлять по завершающему обращение к процедуре двоеточию. В случае же намерений пользователя написать собственную процедуру для подобных целей текст пакетной rand-процедуры может оказаться определенным прообразом. В общем же случае для обеспечения доступа к ГПСЧ рекомендуется использовать конструкции вида Id:= rand({|n|n .. p}), выполнение которых открывает доступ к последовательностям ПСЧ по вызовам Id(), каждое использование которого инициирует получение очередного равномерно распределенного на заданном интервале ПСЧ. Следующий пример иллюстрирует применение рассмотренных средств Maple-языка по работе с псевдослучайными числами:

> rand(); AVZ:= rand(1942 .. 2006): _seed:= 2006: ⇒ 403856185913

> seq(AVZ(), k=1 .. 10); ⇒ 2003, 1960, 1944, 1990, 1986, 1972, 1986, 1992, 2006, 1964

> randomize(2006): _seed; ⇒ 2006

> ПСЧ:= array[1 .. 10]: for k while k <= 10 do ПСЧ[k]:= AVZ() end do:

> seq(ПСЧ[k], k=1 .. 10); ⇒ 2005, 1946, 1958, 1951, 1983, 1992, 1946, 1955, 1995, 1951 > restart; [_seed, rand(), _seed]; ⇒ [_seed, 427419669081, 427419669081]

Наряду с приведенными Maple располагает и другими подобными средствами, в частности, для решения статистических задач (модуль stats), работы со стохастическими объектами (модуль RandomTools), стохастической генерации матриц (linalg[randmatrix]), полиномов (процедура randpoly) и др. Средства поддержки работы с псевдослучайными числами могут быть использованы во многих вычислительных задачах стохастического и квазистохастического характера, а также в задачах, требующих наборов данных для отладки программ и тестирования вычислительных алгоритмов. Рассмотренные средства широко используются нами в различного рода иллюстративных примерах для генерации разнообразных числовых данных.

Выше мы уже не раз употребляли такое понятие как математическое выражение (либо просто выражение), являющееся одним из важнейших понятий Maple, да и математики в целом. Работа с математическими выражениями в символьном виде — основа основ символьной математики. Не меньшую роль они играют и в численных вычислениях. Выражение — центральное понятие всех математических систем. Оно определяет то, что должно быть вычислено в численном или символьном виде. Не прибегая к излишнему формализму, несколько поясним данное понятие. Математические выражения строятся на основе чисел, констант, переменных, операторов, вызовов функций/процедур и различных специальных знаков, например, скобок, изменяющих порядок вычислений. Выражение может быть представлено в общепринятом виде (как математическая формула или ее часть) с помощью операторов, например, с*(х + у^2+sqrt(z)) либо (a+b)/(c+d), оно может определять просто вызов некоторой функции или процедуры F(x,y,z), либо их комбинацию. Используемые в дальнейшем многочисленные иллюстративные фрагменты представят достаточное число примеров на определение допустимых Maple-выражений, что уже практически позволит уяснить данное ключевое понятие пакета.

Наряду с рассмотренными Maple-язык поддерживает работу с рядом других структур данных (стэк, очередь, функциональные ряды, связные графы, графические объекты и т.д.). В этом направлении нами был создан ряд ролезных средств, расширяющих и дополняющих стандартные. В частности, для работы со структурами типа стэк (stack) и очередь (queue), а также введен новый тип структур прямого доступа (dirax) [41,42,103]. Пока же нам будет вполне достаточно приведенных сведений по типам данных и структур данных для понимания сути излагаемого материала, который ссылается на данные понятия. Переходим теперь к средствам Maple-языка, тестирующим вышерассмотренные типы данных, структур данных и выражений.

1.6. Средства тестирования типов данных, структур данных и выражений

Согласно аксиоматике Maple под типом понимается Т-выражение, распознаваемое typeфункцией и инициирующее возврат логического {true|false}-значения на некотором множестве допустимых Maple-выражений. В общем случае Т-выражения языка относятся к одной из четырех групп: (1) системные, определяемые идентификаторами языка {float, integer, list, set и др.}; (2) процедурные, когда тип входит в качестве аргумента в саму тестирующую функцию {type(<Выражение>, <Тип>)}; (3) приписанные и (4) структурные, представляющие собой Maple-выражения, отличные от строковых и интерпретируемые как тип {set(<Id> = float)}. К пятой группе можно отнести типы, определяемые модульными средствами пакета. Несколько детальнее о данной классификации типов будет идти речь ниже по мере рассмотрения все более сложных Maple-объектов. Уже неоднократно упоминавшееся понятие выражения, хорошо знакомое обладающему определенной компьютерной грамотностью читателю, является одним из фундаментальных понятий Maple-языка. Понятие выражения Maple-языка аккумулирует такие рассмотренные конструкции языка как константы, идентификаторы, переменные, данные и их структуры, а также рассматриваемые детально ниже функции, процедуры, модули и т. д. К выражениям в полной мере можно относить, в частности, и такие конструкции языка, как процедуры, ибо их определения допустимо непосредственно использовать при создании сложных выражений. Детальнее на данном вопросе не будем акцентироваться, а отсылаем заинтересованного читателя, например, к таким книгам как [9-14, 29-33, 59-

62, 78-89, 103].

Для определения рассмотренных типов данных и структур данных язык пакета располагает развитыми средствами, базирующимися на специальных тестирующих процедуре whattype и функциях typematch, type, имеющих следующие форматы кодирования:

{type|typematch}(<Maple-выражение>, {<Тип>|<Множество типов>}) whattype(<Выражение>)

где в качестве первого аргумента выступает произвольное допустимое Maple-выражение, а в качестве второго указывается идентификатор требуемого Типа либо их множество. Булева функция {type|typematch} возвращает логическое true-значение, если значение выражения Maple имеет тип, определяемый ее вторым аргументом, и false-значение в противном случае. При определения в качестве второго аргумента множества типов функция {type|typematch} возвращает логическое true-значение в том случае, если тип значения Maple-выражения принадлежит данному множеству, и false-значение в противном случае. При этом, следует помнить, что в качестве второго аргумента может использоваться только множество ({}-конструкция, а не []-конструкция; данное обстоятельство может на первых порах вызывать ошибки пользователей, ранее работавших с пакетом Mathematica, синтаксис которого для списочной структуры использует именно первую конструкцию). Для второго аргумента {type|typematch}-функции допускается более 202 определяемых пакетом типов (Maple 10), из которых здесь рассмотрим только те, которые наиболее употребляемы на первых этапах программирования в Maple и которые непосредственно связаны с рассматриваемыми нами конструкциями языка пакета: идентификаторы, текст, числовые и символьные данные, структуры данных и др. При этом, typematch-функция имеет более расширенные средства тестирования типов, поэтому детальнее она рассматривается несколько ниже.

Наконец, по тестирующей процедуре whattype(<Выражение>) возвращается собственно тип Maple-выражения, определяемого ее фактическим аргументом. При этом, следует отметить, что данная процедура в ряде случаев решает задачу тестирования более эффективно, например для последовательностных структур и в случае неизвестного типа, что позволяет избегать перебора подвергающихся проверке типов. Тут же уместно отметить, что средства тестирования типов, обеспечиваемые, в частности, { typematch| type}-функцией существенно более развиты, чем подобные им средства Mathematica [28-30,32,42,43]. На основе данных средств предоставляется возможность создания достаточно эффективных средств программного анализа типов данных и их структур. В табл. 5 представлены некоторые допустимые Maple-языком типы, тестируемые функцией {type|typematch} и используемые в качестве значений ее второго фактического аргумента, а также их назначение.

Таблица 5

Id типа

тестируемое Maple-выражение; пояснения и примечания:

algnum

алгебраическое число

array

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

Array

массив rtable-типа; дополнительно позволяет проверять вид массива, тип его элементов и другие характеристики

hfarray

массив МАПТ-типа; используется средствами МАПТ

anything

любое допустимое Maple-выражение, кроме последовательности

boolean

логическая константа {true, false, FAIL}

complex

комплексная константа; не содержит нечисловых констант {true, false, FAIL, infinity}, тестирует тип действительной и комплексной частей

complexcons

комплексная константа; a+b*I, где evalf(a) и evalf(b) - float-числа

constant

числовая константа

{odd|even}

{нечетное|четное} целое выражение

float

действительное выражение

fraction

число вида a/b; где a, b - целые числа

indexed

индексированное выражение

infinity

значение бесконечности; +infinity, -infinity, complex infinity

integer

целочисленное выражение

exprseq

последовательность; распознается только whattype-процедурой

{list|set}

{список|множество}; позволяет проверять тип элементов

listlist

вложенный список (ВС); элементы ВС имеют то же число членов

literal

литерал; значение одного из типов integer, fraction, float, string

matrix

матричный объект, массив; дополнительно позволяет проверять вид массива, тип его элементов и др. характеристики

Matrix

матричный объект rtable-типа, массив; дополнительно позволяет проверять вид массива, тип его элементов и другие характеристики

{positive|nega tive|nonneg}

выражение {> 0|< 0|≥ 0}

{posint|negint |nonnegint}

целое выражение {>0|<0|≥0}

numeric

числовое выражение; числовое значение {integer|float| fraction}-типа

protected

protected-свойство; select(type, {unames(), anames(anything)}, 'protected ')

rational

рациональное выражение (дробь, целое)

range

ранжированное выражение; выражение вида a .. b

realcons

действительная константа; включает float-тип и ±infinity

string

строчное выражение

symbol

символ; значение, являющееся не индексированным именем

table

табличный объект; корректно тестирует таблицы, массивы, матрицы

type

тестирует значение на допустимость в качестве типа

vector

вектор, 1-мерный массив; позволяет проверять и тип элементов

Vector

rtable-вектор; позволяет проверять и тип элементов

procedure

процедурный объект

`module`

модульный объект

Смысл большинства типов достаточно прозрачен и особого пояснения не требует. Таблица 5 отражает далеко не полный перечень типов, распознаваемых пакетом. Данный перечень значительно шире и с каждым новым релизом пакета пополняется новыми типами. Например, для Maple 8 этот перечень содержит 176 типов, Maple 9 – 182 и Maple 10 – 202. При этом, пользователь также имеет возможность расширять пакет новыми типами и нами был определен целый ряд новых и важных типов, отсутствующих в Maple. Все они описаны в нашей последней книге [103] и включены в прилагаемую к ней Библиотеку. Ниже мы представим механизм определения пользовательских типов. Следующий достаточно простой фрагмент иллюстрирует применения {type, typematch}-функций и whattype-процедуры:

> [whattype(64), whattype(x*y), whattype(x+y), whattype(a..b), whattype(a::name), whattype([]), whattype(a <= b), whattype(a^b), whattype(Z <> T), whattype(h(x)), whattype(a[3]), whattype({}), whattype(x,y), whattype(table()), whattype(3<>10), whattype(a..b), whattype(47.59), whattype(A and B), whattype(10/17), whattype(array(1 .. 3, [])), whattype(proc() end proc), whattype(a.b), whattype(module() end module), whattype(hfarray(1 .. 3)), whattype("a+b"), whattype(AVZ)];

[integer, *, +, .., ::, list, <=, ^, <>, function, indexed, set, exprseq, table, <>, .., float, and, fraction, array, procedure, function, module, hfarray, string, symbol]

> A:= -64.42: Art:= array(1 .. 3, 1 .. 6): S:= 67 + 32*I: V:= -57/40: L:= {5.6, 9.8, 0.2}: T:= table(): LL:=[[V, 64, 42], [G, 47, 59]]: K:= "Академия": W:= array(1 .. 100): W[57]:= 99:

> [type(A, 'algnum'), type(Art, 'array'), type(`true`, {'boolean', 'logical'}), type(S,

'complex'(integer)), type(56*Pi, 'constant'), type(56/28, 'even'), type(1.7, 'float'), type(A,

'fraction'), type(A*infinity, infinity), type(V, 'integer'), type(L, 'set'(float)), type(LL, 'listlist'), type(Art, 'matrix'), type(AVZ, 'symbol'), type(A,'negative'), type(V,'negint'), type(S,'numeric'), type(A,'rational'), type(infinity, 'realcons'), type(K, 'string'), type(Art, 'table'), type(T, 'table'), type(real, 'type'), type(W, 'vector'), type(hfarray(1 .. 3), 'hfarray')];

[false, true, true, true, true, true, true, false, true, false, true, true, true, true, true, false, false, false, true, true, true, true, false, true, true]

> map(whattype,[H, A, eval(Art),`true`, eval(T)]); ⇒ [symbol, float, array, symbol, table] > map(type, [64, 47/59, 10.17, `H`, G[3], "TRG"], 'literal'); ⇒ [true, true, true, false, false, true]

> map(type, [range, float,set, list, matrix,string,symbol, array,Array, matrix, `..`, `*`], 'type');

[true, true, true, true, true, true, true, true, true, true, true, true]

Приведенный сводный фрагмент охватывает, практически, все типы, представленные выше и тестируемые рассмотренными {type, typematch}-функциями и whattype-процедурой, достаточно прозрачен и особых пояснений не требует. Ранее отмечалось, что whattype-процедура позволяет тестировать последовательностные структуры, тогда как {type|typematch}-функция этого сделать не в состоянии. Более того, в отличие от вторых, whattype-процедура ориентирована, в первую очередь, на тестирование выражений, структурно более сложных, чем данные и структуры данных. При этом, следует иметь в виду, что и данные, и их структуры также можно рассматривать как частный случай более общего понятия выражения.

Так как выражение представляет собой более широкое понятие, чем данные (значения), то для тестирования их типов Maple-язык располагает достаточно развитым набором средств. Прежде всего, для прямого определения типа выражения используется уже упомянутая процедура whattype, имеющая простой формат кодирования: whattype(<Выражение>) и возвращающая тип заданного выражения, если он является одним из нижеследующих:

`*` `+` `.` `..` `::` `<` `<=` `<>` `=` `^` `||` `and` array complex complex(extended_numeric) exprseq extended_numeric float fraction function hfarray implies indexed integer list module moduledefinition `not` `or` procedure series set string symbol table uneval unknown `xor` zppoly Array Matrix SDMPolynom Vector[column] Vector[row]

Приведенный перечень идентифицируемых типов выражений, включая некоторые данные и их структуры, представлен для Maple 10, тогда как для более низких релизов этот перечень несколько короче. Следует еще раз подчеркнуть, что хотя тип последовательности (exprseq) не является определяемым функциями type, typematch типом, однако он идентифицируется whattype-процедурой. При этом, процедура возвращает только тип высшего уровня вложенности выражения в соответствии с приоритетным порядком составляющих его операторов. Следующий пример иллюстрирует применение тестирующей whattype-процедуры:

> [whattype(64), whattype(x*y), whattype(x+y), whattype(x<=y), whattype(a<>b), whattype([]), whattype(a = b), whattype(a^b), whattype(Z), whattype(h(x)), whattype(a[17]), whattype({}), whattype([]), whattype(x,y), whattype(table()), whattype(x..y), whattype(5.9), whattype(A and B), whattype(proc() end proc), whattype(module() end module), whattype(10/17), whattype("SV"), whattype(a.b), whattype(hfarray(1..10))];

[integer, *, +, <=, <>, list, =, ^, symbol, function, indexed, set, list, exprseq, table, .. , float, and, procedure, module, fraction, string, function, hfarray]

При этом, имеют место следующие идентификации типов whattype-процедурой:

{+|-} → + {/|*} → * {>=|<=} → <= {>|<} → < {**|^|sqrt(a)} → ^

{a|a."b"|a.b|a.`b`|`a`.b|`a`.`b`} → symbol {"a"|"a"."b"|"a".`b`|"a".b} → string

{array|vector|matrix} → array

что следует учитывать при использовании указанной процедуры тестирования. Обусловлено это тем обстоятельством, что предварительно вызов процедуры whattype(A) вычисляет и/или упрощает выражение А. Для расширенного тестирования типов выражений служит вышерассмотренная в связи с данными и их структурами функция {type|typematch}. Приведем простой пример на применение type-функции для тестирования выражений:

> [type(sqrt(x)+a/b, 'anything'), type(a**b, `**`), type(x*y+z^a, dependent(z)), type('a.b', `.`), type(x^a-3*x+x^4-47 = b, 'equation'), type(x^3+4*x-56, 'expanded'), type(ifactor(1998), 'facint'), type(h!, `!`), type(F(x), 'function'), type(G[47, 51, 42, 67, 62], 'indexed'), type(5*y^4

+ x^3 - 47*x, 'quartic(y)'), type(arctanh, 'mathfunc'), type(`Salcombe Eesti`, 'symbol'), type(ln, 'mathfunc'), type(10/17, 'numeric'), type(A -> B, 'operator'), type({G = 51, V = 56}, 'point')];

[true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]

Следует упомянуть еще об одной тестирующей функции hastype(expr,t), чей вызов возвращает true-значение, если выражение expr содержит подвыражение заданного t-типа, и false-значение в противном случае, например:

> map2(hastype, (a*x + 10)/(sin(x)*y + x^4), ['integer', 'symbol', 'function', `*`, `+`, symbol^integer]);

[true, true, true, true, true, true]

> map2(hastype, (10^Kr + 96)/(Art^17 + 89), [even^symbol, symbol^odd]); ⇒ [true, true]

При этом, можно проводить тестирование выражений как относительно простых типов, так и структурных, как это иллюстрирует второй пример предыдущего фрагмента.

Дополнительно к рассмотренным средствам тестирования рассмотрим еще 6 весьма полезных процедур из класса так называемых is-процедур языка. Прежде всего, процедура isprime(n) осуществляет стохастическую проверку n-числа на предмет его простоты, тогда как процедура issqr(n) тестирует наличие точного квадратного корня из целого n-числа. Процедура is(V, <Свойство>) тестирует наличие у V-выражения указанного свойства, тогда как процедура ispoly(P, {1|2|3|4}, x) тестирует будет ли P-выражение полиномом {1|2|3|4}-степени по ведущей х-переменной. Процедура isdifferentiable(V,x,n) тестирует V-выражение, содержащее кусочно-определенные функции (abs, signum, max и др.), на предмет принадлежности его к Cn -классу по ведущей х-переменной. Наконец, процедура iscont(W, x=a .. b{, 'closed'}) тестирует факт наличия непрерывности W-выражения на [a, b]-интервале по x-переменной, включая его граничные точки, если закодирован необязательный closed-аргумент. Простой фрагмент иллюстрирует использование указанных is-процедур:

> iscont(x*sin(x) + x^2*cos(x), x= -Pi..Pi, 'closed'); ⇒ true

> isprime(1999), isprime(1984979039301700317); ⇒ true, false

> issqr(303305489096114176); ⇒ true

> x:= 32: is(3*sin(x) + sqrt(10) + 0.42, 'positive'); ⇒ true

> ispoly(3*h^4 - 10*h^3 + 32*h^2 - 35*h + 99, 'quartic', h); ⇒ true

> isdifferentiable(y*(abs(y)*y + signum(y)*sin(y)), y, 2); ⇒ false

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

> type([64, V, sqrt(Art)], [integer, name, `^`]), type(59^sin(x), integer^trig); ⇒ true, true

> type(G(x)^cos(y), function^symmfunc), type(sqrt(F(x)), sqrt(function)); ⇒ true, true

> type(57+sin(x)*cos(y), `&+`(integer, `&*`(trig, symmfunc))); ⇒ true

> type(sqrt(Art^10 + Kr^3 + 99),sqrt(`&+`(name^even, symbol^odd, integer))); ⇒ true

> type((G(x) + cos(y))^(47*I + sin(x)),(`&+`(function, trig)^`&+`(complex, trig(x)))); ⇒ true Из примеров представленного фрагмента вполне прозрачно прослеживается тесная взаимосвязь между структурным деревом термов тестируемого выражения и выбранным для него структурированным типом. Детальнее с вопросами структурированных типов языка можно ознакомиться по книгам [33,42,45,83] либо оперативно по конструкции вида ?type, {anyfunc| identical|specfunc|structure} в среде текущего сеанса пакета.

Следует иметь в виду, что по конструкции следующего простого вида: type('<Id>', name(<Тип>))

предоставляется возможность тестировать тип присвоенного Id-идентификатору значения, как это иллюстрирует следующий весьма простой фрагмент:

> GS:= cos(x)*sin(y) + F(z) + 99: AV:= 42 .. 64: type('AV', name(range)); ⇒ true

> SV:= sqrt(Art + Kr): type('SV', name(sqrt(`&+`(name, symbol)))); ⇒ true

> type('GS', name(`&+`(`&*`(symmfunc, trig), function, odd))); ⇒ true

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

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

и возвращает логическое true-значение только тогда, когда Выражение содержит подвыражения указанного структурированного типа, например:

> Kr:= 10*sin(x) + 17*cos(y)/AV + sqrt(AG^2 + AS^2)*TRG + H^3 - 59*sin(z) - Catalan:

> map2(hastype,Kr, [name^integer,constant,sqrt,fraction,`*`]); ⇒ [true, true, true, true, true]

> map2(has,Kr,[10*sin(x), 1/AV, AG^2+AS^2, H^3,-59*sin(z)]); ⇒ [true, true, true, true, true] > [hasfun(Kr, sin, z), map2(hasfun, Kr, [sqrt, sin, cos])]; ⇒ [true, [false, true, true]]

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

По тестирующей функции has(<Выражение>, <Подвыражение>) производится проверка на наличие вхождения в заданное первым аргументом выражение указанного вторым аргументом подвыражения. Если в качестве второго аргумента has-функции указан список подвыражений, то выражение подвергается тестированию по каждому из подвыражений списка. Функция возвращает true-значение, если факт вхождения установлен, и falseзначение в противном случае. При этом, в случае списка подвыражений в качестве второго аргумента has-функция возвращает true-значение только тогда, когда имеет место факт вхождения в тестируемое выражение по крайней мере одного из подвыражений указанного списка. Второй пример предыдущего фрагмента иллюстрирует сказанное. Данная функция представляет большой интерес в задачах структурного анализа символьных выражений, поступающих в качестве входной информации для процедур символьных обработки и вычислений.

Наконец, по тестирующей функции hasfun(V,<Id-функции> {,x}) возвращается значение true, если определенное первым фактическим аргументом V-выражение содержит вхождение функции, заданной своим идентификатором, и, возможно, от указанной третьим необязательным аргументом ведущей х-переменной. Последний пример предыдущего фрагмента не только иллюстрирует вышесказанное, но и указывает на то, что, в частности, sqrt-функция не тестируется hasfun-функцией. Ряд дополнительных вопросов, относящихся к тестированию выражений, рассматривается несколько ниже.

Начиная с Maple 8, пакет пополнен модулем TypeTools, пять процедур которого служат для расширения списка типов, стандартно прддерживаемых type-функцией. Со своей стороны, еще раньше мы определили процедуру usertype, обеспечивающую ряд массовых операций для работы с пользовательскими типами, поддерживаемых процедурами с именами формата 'type/name', where name – имя пользовательского типа.

Модуль TypeTools может быть использован для расширения стандартной type-функции пакета типами, определенными пользователем, однако его применение приводит к созданию двух непересекающихся систем типизации в среде Maple, а именно: (1) системе типов пользователя, базирующейся на конструкциях вида `type/name`:=proc ... end proc, сохраненных в Maple-библиотеках, и (2) системе, базирующейся на модуле TypeTools.

Процедура usertype может оказаться достаточно полезным средством при организации работы с пользовательскими типами, имена определений которых имеют формат вида `type/name`. Следующий фрагмент представляет исходный текст и примеры применения процедуры usertype.

usertype := proc() local a b c d k j h, , , , , , ; if nargs < 2 then if nargs = 0 then assign67(h = [libname], d = NULL) elif args 1[ ] = 'active' then assign(a = [anames('procedure')], d = [ ]); for k in a do

if "" || k[1 .. 5] = "type/"then d := [op(d), "" || "" || [k 6 .. -1]] end if

end do;

return sort(map(convert, d, 'symbol'))

elif type(args 1[ ], {'mlib', 'mla'}) then assign67(h = [args 1[ ]], d = NULL) else error "<%1> is not a Maple library", args 1[ ] end if;

for j in h do

assign('a' = march('list', j), 'b' = [ ]); for k in a do

c := Search2(k[1], {".m" "type/", }); if c[1] = 1 and c[2] = length(k[1]) − 1 then b := [op(b), cat(``, k[1][6 .. -3])]

end if

end do; d := d j, , sort(b)

end do;

d

elif nargs = 2 then if type(args 1[ ], 'symbol') and args 2[ ] = 'code' then try type(a, args 1[ ]) catch "type `%1` does not exist":

error "type `%1` does not exist",args 1[ ]

end try ; eval(`type/` || args 1[ ])

elif type(args 1[ ], {'mlib', 'mla'}) and

type(eval cat(( `type/`, args 2[ ])), 'boolproc') then UpLib(args 1[ ], [cat(`type/`, args 2[ ])]) end if

elif type(args 1[ ], {'mlib', 'mla'}) and type(eval cat(( `type/`, args 2[ ])), 'boolproc') and args 3[ ] = 'del' then march('delete', args 1[ ], cat(`type/`, args 2[ ]))

else error "factual arguments are invalid", [ args] end if

end proc

> usertype("C:/Program Files/Maple 7/LIB/UserLib");

"C:/Program Files/Maple 7/LIB/UserLib", [Lower, Table, Upper, arity, assignable1, binary, rlb, boolproc, byte, complex1, digit, dir, dirax, file, file1, fpath, heap, letter, libobj, lower, mla, mlib, mod1, nestlist, nonsingular, package, path, plot3dopt, plotopt, realnum, sequent, setset, ssign, upper]

> usertype('active'); ⇒ [dir, mla, mlib]

Требуемая функция процедуры usertype может быть получена использованием ее вызовом на кортеже соответствующих фактических аргументов, а именно:

аргументы отсутствуют – возвращает списки всех имен типов формата `type/name`, на- ходящихся во всех Maple-библиотеках, определяемых предопределенной libname- переменной. Данная функция позволяет получать только имена типов, зарегист- рированных, используя вышеуказанный метод. В частности, она не может исполь- зоваться для получения встроенных типов и созданных на основе TypeTools-моду- ля. Каждый возвращаемый вышеуказанный список предваряется полным путем к Maple-библиотеке, содержащей определения типов, чьи имена находятся в списке; 'active' – возвращает список имен вида `type/name`, активных в текущем сеансе;

L – возвращает список имен типов вида `type/name`, расположенных в Maple- библиотеке, определенной полным путем L к ней. Список предваряется полным путем к Maple-библиотеке, содержащей определения типов; name, 'code' – возвращает исходный текст процедуры `type/name`;

L, name – помещает определение типа `type/name` в Maple-библиотеку, определен- ную полным путем L к ней. Процедура `type/name` должна иметь boolproc- тип. В данном случае вызов процедуры usertype(L,name) выводит соответ- ствующие сообщения;

L, name, 'del' – удаляет определение типа `type/name` из Maple-библиотеки, полный туть к которой определен аргументом L. Никаких сообщений не выводится.

В процессе использования для Maple релизов 6-7 процедура usertype проявила себя достаточно эффективным средством.

Еще на одном весьма существенном моменте следует акцентировать ваше внимание. В среде пакета вызов type(Х, 'type') тестирует выражение Х на предмет быть допустимым выражением типа, т.е. опознаваемым встроенной type-функцией в качестве типа. При этом, по определению под выражением типа Х понимается такое выражение, для которого успешно выполняется вызов type(<Выражение>, Х), где в качестве первого аргумента выступает произвольное Maple-выражение. В качестве типов допускаются системные типы {integer, float, symbol, string и др.}, типы, определяемые процедурами с именами формата `type/name`, а также типы, определяемые присвоением либо комбинацией типов. Однако, данная проверка не столь корректна, как декларируется. Следующий простой фрагмент подтверждает вышесказанное.

> restart; with(TypeTools); ⇒ [AddType, GetType, GetTypes, RemoveType, Type] > AddType(listodd, L -> type(L, 'list'(odd)));

> type([64, 59, 10, 17, 39], 'listodd'), type([59, 17, 39], 'listodd'); ⇒ false, true > type(listodd, 'type'); ⇒ true

> `type/listeven`:= L -> type(L, 'list'('even')): type(listodd, 'type'); ⇒ true

> type([64, 10, 44], 'listeven'), type([59, 17, 39], 'listeven'); ⇒ true, false

> `type/neg`:= proc(x::{integer, float, fraction}) if x < 0 then true else false end if end proc: > map(type, [64, -10, 59, -17, 39, -44], 'neg'), type(neg, 'type');

[false, true, false, true, false, true], false

> UpLib("С:/Temp/userlib", [`type/neg`]): restart; libname:= libname, "С:/Temp/userlib":

type(neg, 'type'); ⇒ false

> map(type, [64, -10, 59, -17, 39, -44], 'neg'); ⇒ [false, true, false, true, false, true]

type/Type := proc(x::anything)

local a; try type(a x, ) catch "type `%1` does not exist": return false

catch : return true end try; true

end proc

> type(neg, 'type'), type(neg, 'Type'); ⇒ false, true

> map(type, [neg, aa, bb, set, mla, dir, file], 'Type'); ⇒ [true, false, false, true, true, true, true]

В первой части производится тестирование типов, определенных средствами TypeToolsмодуля и процедурами с именами вида `type/name`. Оказывается, что во втором случае вызов type(neg, 'type') некорректно тестирует neg-тип. Затем определяется `type/Type`процедура, устраняющая данный недостаток. В заключительной части фрагмента данная процедура проверяется на предмет корректности, демонстрируя, что она решает задачу тестирования типов успешнее стандартного средства, обеспечивая корректное тестирование типов, определенных любым допустимым в Maple способом.

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

1.7. Конвертация Maple-выражений из одного типа в другой

Так как многие типы числовых значений взаимно обращаемы, то Maple-язык пакета для конвертации одного типа в другой располагает достаточно развитыми средствами, базирующимися на многоаспектной convert-функции, которая служит не только для конвертации значений из одного типа в другой, но и конвертации выражений в целом из одного типа в другой, при этом понятие типа трактуется существенно более широко, чем мы говорили до сих пор, например, можно конвертировать выражения, содержащие волновые Айри-функции, в выражения с функциями Бесселя и т.д. В общем случае функция convert имеет следующий формат кодирования: convert(<Выражение>, <Формат> {, <Список опций>})

где выражение представляет собственно сам объект конвертации, формат определяет тип конвертации (его в случае столь широкого толкования понятия “тип” вполне допустимо называть более емким понятием “формат“), а необязательный третий аргумент функции составляет Список опций, определяемых спецификой используемого второго Формат-аргумента функции. Язык пакета для convert-функции в качестве значения ее второго аргумента допускает следующие форматы (типы) конвертации:

0F1 1F1 2F1 Abel Abel_RNF abs Airy algebraic and arabic arctrig arctrigh I array base Bessel Bessel_related binary binomial boolean_function boolean_operator bytes Chebyshev compose confrac conversion_table Cylinder D decimal egrees DESol diff dimensions disjcyc Ei_related elementary Elliptic_related equality erf erfc erf_related exp expln expsincos factorial FirstKind float fullparfrac GAMMA function_rules GAMMA_related global Hankel eaviside eun hex hexadecimal Int hypergeom int iupac Kelvin Kummer Legendre linearODE list listlist ln local mathorner Matrix

matrix MeijerG metric MobiusR MobiusX MobiusY mod2 ModifiedMeijerG multiset name NormalForm numericproc octal or package parfrac permlist piecewise polynom PLOT3Doptions PLOToptions polar POLYGONS power pwlist radians radical set rational ratpoly RealRange record relation Riccati roman RootOf SecondKind std

signum sincos sqrfree StandardFunctions stdle string Sum sum surd symbol tan

system table temperature to_special_function trig trigh truefalse units unit_free Vector vector Whittaker windchill xor y_x `*` `+`

Уже из простого перечисления допустимых видов конвертации (в количестве 137 для пакета Maple 10) следуют весьма широкие возможности convert-функции. Многие из них будут достаточно детально рассмотрены в настоящей книге в различных контекстах, с другими можно детально ознакомиться по справочной системе пакета либо по книгам [34,42,56,63,78,96]. В представительном отношении набор допустимых форматов конвертации относительно предыдущих релизов пакета в Maple 10 увеличился, да и в качественном отношении произведено существенное расширение механизма конвертации типов. Данное обстоятельство существенно расширило возможности Maple-языка по преобразованию типов и форматов представления выражений. Следующие довольно распространенные форматы конвертации типов выражений представлены в табл. 6.

Таблица 6

Формат-значение

Конвертация первого аргумента функции в формат:

RootOf

в терминах RootOf-нотации; конвертирует все I и радикалы

radical

в терминах I и радикалов; обратный к предыдущему формату

{and|or}

{and|or}-представления, если аргумент - список/множество

array

структуры типа массив; конвертирует списки, таблицы, массивы; результат возвращается в уплотненном формате

base

из одной системы счисления в другую; имеет две формы

binary

бинарного числового представления

binomial

GAMMA-функции и факториалы в binomial-функцию

bytes

байтов; конвертация списка 16-ричных цифр или строк в байты

confrac

бесконечной дроби; перевод чисел, рядов, алгебраических выражений в бесконечно-дробную аппроксимацию

{`*`|`+`}

{произведения|суммы} всех операндов исходного выражения

decimal

2-, 8- и 16-ричные (в виде строк) числа в 10-ричные

degrees

градусного представления; обратным к нему является radians

double

float-числа двойной точности в другие форматы; поддерживается конвертация между платформами IBM, VAX, MIPS

{equality|lessthan| lessequal}

равенства или отношения; {отношение} →{=|<|≤}

exp

тригонометрические функции в экспоненциальные

expln

элементарные функции в терминах функций {exp, ln}

expsincos

тригонометрические функции в терминах {exp, sin, cos}

Ei

тригонометрические, гиперболические и логарифмические интегралы в экспоненциальные интегралы Ei(x)

factorial

конвертация GAMMA-функции и биномиалов в факториалы

float

float-типа; в значительной мере подобна evalf-функции

fullparfrac

рациональное выражение в полностью линейное рациональное

hex

десятичное неотрицательное число в 16-ричное строчное

horner

полинома в форме Горнера

list

таблицы, векторы или выражения в списочную структуру (в случае выражения элементами списка будут его операнды)

listlist

вложенного списка; конвертируются списки/массивы

ln

обратные тригонометрические функции в логарифмические

mathorner

полином в матричную форму Горнера

matrix

массив или вложенный список в матричную структуру

metric

английскую систему мер в метрическую

mod2

приведения по (mod 2); допустимо вхождение {and, or, not}

multiset

в специальную multiset-форму

{symbol|name}

конвертация в {symbol|name}-тип; symbol - синоним name

numericproc

символьную F(x,y)-функцию в числовую F`(x,y)-функцию

octal

8-ричного представления; допустимо определение точности

parfrac

перевод в частично-дробный формат; расширенные опции

piecewise

в формат кусочно-определенной функции

polar

комплексные числа в полярную форму представления

pwlist

конвертация кусочно-определенной функции в список

radians

перевод из радианной меры в градусную; обратная к degrees

{rational|fraction}

перевод из float-формата в приближенный рациональный вид

set

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

{signum|abs}

замена всех {abs|signum}-функций выражения на {signum|abs}

sincos

тригонометрические функции в {sin, cos, sinh, cosh}

sqrfree

представление полиномов без квадратов

string

конвертация выражения в строчный формат

table

перевод списочной структуры или массива в табличную форму

tan

перевод тригонометрических функций в tan-представление

trig

экспоненциальные и тригонометрические функции в форме выражений из функций {exp, sinh, cosh, tanh, sech, csch, coth}

vector

список или массив в вектор, а в общем случае и в матрицу

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

> convert(table([Kr, G, S, Art]), 'array'); ⇒ [Kr, G, S, Art]

> convert([2, 0, 0, 6], 'base', 10, 2); ⇒ [0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1]

> convert(2006, 'binary'); ⇒ 11111010110

> convert(Tallinn, 'bytes'); ⇒ [84, 97, 108, 108, 105, 110, 110]

> convert([73, 110, 116, 101, 114, 110, 97, 116, 105, 111, 110, 97, 108, 32, 65, 99, 97, 100, 101, 109, 121, 32, 111, 102, 32, 78, 111, 111, 115, 112, 104, 101, 114, 101], 'bytes');

"International Academy of Noosphere"

2

> convert(sin(x)/x, 'confrac', x, 8); ⇒ 1 + x 2

−6 + x 2

10 11 x

− +

3 126

> convert(x*y*z, `+`), convert(x*y*z – h + 64, `*`); ⇒ x + y + z, -64 x y z h

> [convert(101100111, 'decimal', 'binary'), convert(64, 'decimal', 'octal'), convert(`ABCDEF`, 'decimal', 'hex')]; ⇒ [359, 52, 11259375]

> convert(y*sin(x) + x*cos(y), 'exp'); ⇒ −1 I y  e(x I)e ( 1 x I)  + x 1 2 e(y I) + 1 2 e ( 1 y I) 

2

> convert([[V, 42, 64], [G, 47, 59], [Art, 89, 96]], 'matrix'); ⇒  Art GV 42 4789 64 5996 

> convert([inch, ft, yard, miles, bushel], 'metric');

 1275000m, 3811250m, 11431250m, 2514615625km, 0.035238775147289395200 m3 

> convert([Art, Kr, Sv, Arn, V, G], `and`); ⇒ Art and Kr and Sv and Arn and V and G > G:= (x, y) -> x*sin(y): convert(G(x, y), 'numericproc'); proc(_X, _Y) local err; err := traperror evalhf(( (x×sin( )y )(_X _Y, )));

if type [( err], [numeric]) then err

else

err := traperror(evalf((x×sin(y))(_X, _Y))); if type [( err], [numeric]) then err else undefined end if

end if

end proc

> convert(a*x + b*y - 3*x^2 + 5*y^2 - 7*x^3 + 9*y^3, 'horner', [x, y]); (b + (5 + 9 y) y) y + (a + (-3 - 7 x) x) x

С учетом сказанного приведенный фрагмент использования вышерассмотренных пакетных средств конвертации типов (форматов) выражений в широком понимании данного термина представляется достаточно прозрачным и особых пояснений не требует. Более детальное ознакомление со средствами данного класса Maple-языка рекомендуется проводить при непосредственной практической работе в его среде. Нами также был определен ряд довольно полезных средств конвертации объектов Maple-типа в объекты типа rtable, и наоборот [103]. Например, вызов нижеприведенной процедуры avm_VM(G) возвращает результат преобразования объекта, определенного фактическим аргументом G типа {array, vector, matrix}, в объект типа {Vector, Matrix}, и наоборот – объект типа {Vector, Matrix} в векторы или матрицы типа Maple.

avm_VM := proc(G::{Matrix, Vector, matrix, vector, array}) local k h S H M, , , , ;

S := H → rtable(op 2,( eval(G)), {op(op 3,( eval(G)))}, 'subtype' = H);

`if`(type(G, 'array') = true and nops [( op 2,( eval(G))]) = 1, S(Vector['row']),

`if`(type(G, 'array') = true and nops [( op 2,( eval(G))]) = 2 and nops([seq(k, k = op(2, eval(G))[1])]) = 1, rtable(

op [( assign(h = op 3,( eval(G))), [seq(rhs(h k[ ]), k = 1 .. nops( )h )]]),

'subtype' = 'Vector'['row']), `if`( type(G, 'array') = true and nops [( op 2,( eval(G))]) = 2 and nops [( seq(k, k = op 2,( eval(G))[ ]2 )]) = 1, rtable( op([assign(h = op(3, eval(G))), [seq(rhs(h[k]), k = 1 .. nops(h))]]), 'subtype' = 'Vector'['column']), `if`(whattype(G) = 'Matrix', op([assign(M = Matrix(op 1,( G), op 2,( G), subs('readonly' = NULL, MatrixOptions(G)))), matrix(op 1,( G), [op(op 2,( G))])]), `if`(whattype(G) = 'Vector'['row'], vector(`if`(whattype(op 1,( G)) = `..`, rhs(op 1,( G)), op 1,( G)), op([assign(h = op(2, eval(G))), [seq(rhs(h[k]), k = 1 .. nops(h))]])), `if`( whattype(G) = 'Vector'['column'], matrix(

`if`(whattype(op 1,( G)) = `..`, rhs(op 1,( G)), op 1,( G)), 1, op [( assign(h = op 2,( eval(G))), [seq(rhs(h k[ ]), k = 1 .. nops(h))]])),

S Matrix( )))))))

end proc

При этом, следует отметить, что в нашей книге [12] (прилож. 1) представлен целый ряд особенностей выполнения конвертации выражений одного типа в другой посредством convert-функции, имеющих важное значение при практическом использовании данного средства. Там же приведены и другие полезные замечания относительно convert-функции, сохраняющие свою актуальность и для последующих релизова Maple.

Следует отметить, что в таблицах главы и в последующих не приводится исчерпывающей характеристики функций либо других средств Maple-языка, а только основные их назначение и аргументы. Некоторые их особенности приводятся в прилож. 3 [12], полную же информацию по любой функции, поддерживаемой пакетом, можно оперативно получать по конструкции ?<функция> или в документации по Maple, например, [8183,89]. Следует еще раз напомнить, что кодирование идентификаторов в пакете Maple регистро-зависимо, поэтому необходимо правильно кодировать все используемые идентификаторы. Несоблюдение данного условия лежит в основе многих ошибок.

1.8. Функции математической логики и средства тестирования пакета

Для решения задач математической логики, пропозиционального исчисления, а также организации логических конструкций, управляющих вычислительным процессом в документе Maple либо программе (условные переходы, ветвления, циклические и итеративные вычисления и др.), Maple-язык располагает рядом встроенных, библиотечных и модульных процедур и функций, операторов и иных конструкций, значения аргументов или возвращаемых результатов которых получают логические значения true (истина), false (ложь) и FAIL (неопределенность). К группе данных средств относятся и так называемые тестирующие функции и процедуры. Такие средства в зависимости от результата тестирования своего аргумента возвращают значение true или false. Ряд свойств таких функций нами рассматривался выше в связи с другими вопросами Maple-языка, остальные будут рассматриваться здесь и в последующих разделах по мере необходимости и в контексте с различными вопросами практического программирования.

В основе математической логики, поддерживаемой Maple-языком, лежит понятие булевого (boolean; логического) выражения. Как уже отмечалось, Maple-язык поддерживает трехзначную логику {true, false, FAIL} для всех булевых операций. Булевы выражения формируются на основе базовых логических операторов {and, or, not}, образующих функционально полную систему (в смысле возможности представления на их основе произвольной логической функции) и операторов отношения {< (меньше)|<= (не больше)|> (больше)|>= (не меньше)|= (равно)|<> (не равно)}. Булевский (boolean) тип идентифицируется тестирующими функциями type и typematch, и процедурой whattype; и при этом, Maple-язык дифференцирует boolean-тип на два базовых подтипа: relation и logical. К relation-подтипу относятся выражения вида {<|<=|=|<>}, а к logical-подтипу - выражения вида {or, and, not}; тогда как boolean-тип определяют как собственно выражения двух указанных подтипов, так и их сочетания, а также логические константы {true, false}. Все логические типы тестируются {type|typematch}-функцией следующего формата кодирования:

{type|typematch}(<Выражение>, {boolean|relation|logical})

как это иллюстрирует следующий простой пример:

> [type(AV <> 64, 'relation'), type(AV and AG, 'logical'), type(true <> false, 'boolean')],

[whattype(AV and AG), typematch(AV <> 59, 'relation')]; ⇒ [true, true, true], [and, true] Для вычисления Maple-выражений в булевой трактовке используется evalb-функция, имеющая простой формат кодирования: evalb(<Выражение>) и возвращающая логическое значение {true|false|FAIL}; если это невозможно, то выражение возвращается невычисленным. Основной задачей evalb-функции является вычисление Maple-выражений, содержащих операторы отношения, в терминах логических значений. Это необходимо в целом ряде случаев, связанных с различного рода задачами анализа вычислительных конструкций, ибо Maple-язык трактует выражения, содержащие операторы отношения, как алгебраические уравнения или неравенства, если выражения дополнительно не содержат логических {and, or, not}-операторов. И только в качестве аргументов evalb-функции либо в {if|while}-предложениях Maple-языка они получают логическую трактовку. При этом, следует иметь в виду, что Maple-язык конвертирует выражение, содержащее операторы отношения {>, >=}, в эквивалентное ему выражение в терминах {<, <=}-операторов. Более того, т.к. evalb-функция не производит упрощения выражения-аргумента, то в ряде случаев ее применение может приводить к некорректным результатам. Поэтому, перед вызовом evalb-функции рекомендуется предварительно упрощать выражение, передаваемое ей в качестве фактического аргумента; это можно, в частности, делать и по simplify-функции. Простой фрагмент иллюстрирует вышесказанное:

> whattype(AVZ = AGN), evalb(AVZ = AGN), evalb(AVZ = AVZ); ⇒ =, false, true

> AV:= 64: whattype(AV <= 64), evalb(AV = 64), evalb(AV <= 64); ⇒ <=, true, true > whattype(sqrt(64) <> ln(17)), evalb(sqrt(64) <> ln(17)); ⇒ <>, true

Следует иметь в виду, что вычисление логических выражений подчиняется следующему правилу: первым вычисляется левый операнд {and|or}-оператора и вычисление правого его операнда производится только тогда, когда логическое значение левого операнда может способствовать получению true-значения выражения в целом. Так, правый операнд логического and-выражения следующего вида:

> G:= 0: V:= 17: if (G = 64) and (V/G >= 0.25) then printf(`%3s\%4f`, `G=`, V/G) end if;

> G:= 0: V:= 20: if (G = 52) or (V/G >= 9.47) then printf(`%3s\%4f`, `G=`, V/G) end if; Error, numeric exception: division by zero не вычисляется и не вызывает ошибочной ситуации “деления на нуль”, т.к. левый его операнд (G = 64) для G = 0 при вычислении возвращает false-значение, не способствующее получению true-значения and-оператора в целом, независимо от результата вычисления его правого операнда, вызывающего на данном G-значении указанную ошибочную ситуацию. Иная ситуация, как показано, имеет место для случая or-оператора.

Правила выполнения логических {and,or,not}-операторов на {true, false, FAIL}-значениях в качестве операндов определяются следующими таблицами истинности:

and

true

false

FAIL

or

true

false

FAIL

not

true

true

false

FAIL

true

true

true

true

true

false

false

false

false

false

false

true

false

FAIL

false

true

FAIL

FAIL

false

FAIL

FAIL

true

FAIL

FAIL

FAIL

FAIL

Смысл приведенных правил достаточно прозрачен и пояснений не требует, например: > [FAIL and true, false or FAIL, true or FAIL, not FAIL]; ⇒ [FAIL, FAIL, true, FAIL]

Следует отметить, что до 6-го релиза Maple для расширения круга решаемых задач математической логики и ее приложений дополнительно предоставлял 10 модульных функций, поддерживаемых средствами logic-модуля. Однако, после нашей принципиальной критики ряда его функций, точнее результатов вызовов bequal-функции на FAIL-значениях [10-12], данный модуль был исключен из Maple. И аналога этого (в целом весьма полезного) модуля не было до Maple 10. И только в последнем релизе появился модуль Logic, чьи средства предна-начены для работы с выражениями, используя двузначную булеву логику, т.е. без FAIL-значения. По конструкции ?Logic читатель может детально ознакомиться со средствами данного модуля.

Bit := proc(

B::{string symbol, , list(integer)}, bits::{procedure, list {( equation integer, })}) local a b c k, , , ; c := x → [seq(parse(x k[ ]), k = 1 .. length( )x )]; if type(B, {'symbol', 'string'}) and length(B) = 1 then a := c(cat("", convert(op(convert(B, 'bytes')), 'binary'))); b := [0 $ (' 'k = 1 .. 8 − nops(a)), op(a)]

elif type(B, 'list'('integer')) and nops(B) = 1 and belong(B[1], 0 .. 255) then a := c(cat "",( convert(op(B), 'binary'))); b := [0 $ (' 'k = 1 .. 8 − nops(a)), op(a)]

else error "1st argument must be symbol/string of length 1 or integer list, but \ as received <%1>", B

end if; if type(bits, 'list'('integer')) and belong {( op(bits)}, 1 .. 8) then

[seq(b k[ ], k = {op(bits)})] elif type(bits, 'list'('equation')) and belong({seq(lhs(k), k = bits)}, 1 .. 8) and belong {( seq(rhs( )k , k = bits)}, 0 .. 1) then seq(assign(' 'b[lhs(bits k[ ])] = rhs(bits k[ ])), k = 1 .. nops(bits)); convert(parse cat(( op map(( convert b, , 'string')))), 'decimal', 'binary')

elif type(bits, 'procedure') then convert(parse(cat(op(map(convert, bits(b), 'string')))), 'decimal', 'binary')

else error "2nd argument <%1> is invalid", bits end if

end proc

> Bit(`S`,[k$k=1..8]),Bit([59],[1,3,8]), Bit([59],[2=1,4=0,8=0]),Bit("G",[6]),Bit("0",[6=1,7=1,8=1]);

[0, 1, 0, 1, 0, 0, 1, 1], [0, 1, 1], 106, [1], 7

> R:=(L::list) -> [L[nops(L)-k]$k=0..nops(L)-1]: Bit("G", [k$k=1..8]), Bit("G", R), Bit("S", R), Bit([59], R); ⇒ [0, 1, 0, 0, 0, 1, 1, 1], 226, 202, 220

Для обеспечения логических операций и по-битной (поразрядной) обработки информации нами также был определен ряд процедур и модулей [42,103,109]. Так вышеприведенная процедура Bit(B, bits) обеспечивает выполнение следующих базовых по-битных операций с символом, указанным первым аргументом В (в качестве В могут выступать 1-элементные строка либо символ, а также 1-элементный список, чей элемент определяет десятичный код символа из диапазона 0..255):

(1) если второй аргумент bits имеет вид [n1, n2, …], вызов процедуры Bit(B, bits) возвращает значения битов символа B, расположенных в его позициях nj (nj лежит в 1 .. 8); (2) если второй аргумент bits имеет вид [n1 = b1, n2 = b2, …], возвращается десятичный код символа, полученного заменой значений битов символа В (в его позициях nk) на бинарные значения bk (nk = 1..8; bk = {0|1}).

(3) если второй аргумент bits – имя процедуры, определенной над списками, возвращается результат применения процедуры к списку значений всех битов символа В.

Наконец, в двух последних случаях возвращается десятичный код символа – результата обработки исходного символа В.

Базовые средства поддержки булевой алгебры обеспечиваются модулем boolop, а также нижеследующей процедурой BitWise [103,108,109]:

BitWise := proc(BO::symbol, n::nbinary) local a b c d k h, , , , , ; assign(a = length( )n , c = "", h = 0);

if not member(BO, {AND ADD OR NOR XOR NOT IMPL, , , , , , }) then error

"operation <%1> is different from {ADD, AND, OR, XOR, NOR, NOT, \

IMPL}", BO elif BO = NOT then

parse(cat(seq(`if`(k = "0" "1" "0", , ), k = convert(n, 'string'))))

else if 2 < nargs and type(args 3[ ], 'nbinary') then assign(b = length args[3]( ))

else error "3rd argument is absent or has a type different from nbinary" end if;

if a = b then assign(' 'a = "" || n, ' 'b = "" || args 3[ ])

elif a < b then assign(' 'b = "" || args 3[ ], ' 'a = cat(seq "0",( k = 1 .. b − a), n)) else assign(' 'a = "" || n, ' 'b = cat(seq "0",( k = 1 .. a − b), args 3[ ])) end if;

if BO = ADD then for k from length( )a by -1 to 1 do d := map(parse, {b k[ ], a k[ ]}); if d = {0, 1} then if h = 1 then c := cat("0", c) else assign(' 'c = cat "1",( c), ' 'h = 0) end if

elif d = {1} then if h = 1 then c := cat "1",( c) else assign('c' = cat("0", c), 'h' = 1) end if

else assign(' 'c = cat "",( parse(a k[ ]) + h c, ), ' 'h = 0) end if

end do; parse(c)

elif BO = OR then parse( cat(seq(`if`([a k[ ], b k[ ]] = ["0" "0", ], "0" "1", ), k = 1 .. length(a))))

elif BO = NOR then parse( cat(seq(`if`([a k[ ], b k[ ]] = ["0" "0", ], "1" "0", ), k = 1 .. length(a))))

elif BO = XOR then parse(cat(seq(

`if`([a k[ ], b k[ ]] = ["0" "0", ] or [a k[ ], b k[ ]] = ["1" "1", ], "0" "1" ,, ) k = 1 .. length(a))))

elif BO = AND then parse( cat(seq(`if`([a k[ ], b k[ ]] = ["1" "1", ], "1" "0", ), k = 1 .. length(a))))

else parse( cat(seq(`if`([a k[ ], b k[ ]] = ["1" "0", ], "0" "1", ), k = 1 .. length(a))))

end if

end if

end proc

> BitWise(ADD, 1001, 100101101), BitWise(NOT, 1001), BitWise(AND, 1001, 100101101),

BitWise(XOR, 100101101, 1001), BitWise(OR, 100101101, 1001), BitWise(IMPL, 1001, 1001); 100110110, 110, 1001, 100100100, 100101101, 1111

Вызов процедуры BitWise(BO, n {, m}) возвращает результат поразрядной операции ВО над бинарными числами n {и m}. При этом в качестве ВО выступают традиционные булевы операции {AND, OR, XOR, NOR, NOT, IMPL}, где IMPL обозначает implies-операцию; тогда как ADD – операция поразрядного сложения по mod 2.

Так модуль boolop [109] экспортирует ряд полезных функций/операторов, обеспечивающих базовые операции булевой алгебры, а именно:

&andB, &orB, &xorB, &notB, &impB

где, в частности, &andB – n-арная логическая функция/оператор and:

boolop:-&andB := proc(x::{0, 1}, y::{0, 1}) local a k, ; if nargs = 0 or nargs = 1 then error "actual arguments in single number or are absent"

elif nargs = 2 then if [x y, ] = [1, 1] then 1 else 0 end if else if type {( args 3 .. -1[ ]}, set {( 0, 1})) then a := procname(x y, ); for k from 3 to nargs do a := procname(a, args[ ]k ) end do; a

else error "arguments starting with 3rd should have binary type but had re\ ceived %1" [, args 3 .. -1[ ]]

end if

end if

end proc

> with(boolop): 0 &andB 0, 1 &andB 1, 0 &andB 1, 1 &andB 0, `&andB`(1, 0, 1, 0, 1, 0);

0, 1, 0, 0, 0

Следующая процедура logbytes обеспечивает поддержку базовых логических операций {xor, and, or, not, implies} над последовательностями байтов типа {string, symbol}.

logbytes := proc(O::symbol) local a k j, , ; if nargs = 1 then error "number of arguments must be > 1" elif not member O, {( `and` `not` `or` `xor` `implies`, , , , }) then error "1st argu\ ment must be {`and`,`or`,`xor`,`not`,`implies`} but has received <%1>O",

else a := { };

for k from 2 to nargs do

if (proc( )x try type(x, {'symbol', 'string'}) and type(x, 'byte') catch : return false end try

end proc ) args[ ]( k ) then next else a := {k, op( )a }

end if

end do

end if; if a ≠ { } then error "arguments with numbers %1 must be bytes of type {string, symbol}", a

else a := map(Bit, [args 2 .. -1[ ]], [' 'k $ (' 'k = 1 .. 8)])

end if;

if O = `and` then xbyte1(

[seq(boolop[boolop `&andB`:- ](seq(a k[ ][ ]j , k = 1 .. nops( )a )), j = 1 .. 8)]) elif O = `or` then xbyte1(

[seq(boolop[boolop `&orB`:- ](seq(a k[ ][ ]j , k = 1 .. nops(a))), j = 1 .. 8)]) elif O = `xor` then xbyte1(

[seq(boolop[boolop `&xorB`:- ](seq(a k[ ][ ]j , k = 1 .. nops(a))), j = 1 .. 8)]) elif O = `not` then xbyte1 [( seq(boolop[boolop `&notB`:- ](a[ ]1 [ ]j ), j = 1 .. 8)]) elif O = `implies` and 2 < nargs then xbyte1(

[seq(boolop[boolop `&impB` :- ](seq(a k[ ][ ]j , k = 1 .. 2)), j = 1 .. 8)]) else 'procname(args)'

end if

end proc

> logbytes(`and`, A,v,z, A,G,N, V,S,V), logbytes(`or`, A,v,z, A,G,N, V,S,V), logbytes(`xor`, A,v,z, A,G,N, V,S,V); ⇒ "@", "", "V"

Логические операции производятся над соответствующими битами байтов, определенных последовательностью аргументов, начиная со второго. Тогда как в качестве первого О-аргумента выступают указанные логические операции. Результат возвращается в виде байта в string-формате. При этом, предполагаются n-арные операции {`xor`,`and`,`or`}, бинарная операция `implies` и унарная `not`.

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

Тестирующие функции, значительная часть которых рассмотрена выше, возвращают значение {true|false} в зависимости от {истинности|ложности} того или иного проверяемого логического условия (теста). И в этом смысле они могут входить в состав булевых выражений. Выше был рассмотрен целый ряд тестирующих функций, обеспечиваемых языком пакета. В дальнейшем оставшиеся функции данного типа будут представлены при обсуждении соответствующих приложений Maple-языка.

Здесь мы несколько расширим наше представление о концепции типов, поддерживаемой пакетом. Как уже отмечалось выше, базовыми тестирующими средствами являются функции type и typematch, а также процедура whattype. Третья из них возвращает поддерживаемый Maple тип указанного своим фактическим аргументом Maple-выражения, тогда как две первые возвращают {true|false}-значение в зависимости от {истинности| ложности} факта эквивалентности типа, указанного их первым фактическим аргументом (выражение) и их вторым фактическим аргументом – идентификатором типа, распознаваемого Maple-языком.

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

algebraic anything applied array boolean equation even float fraction function list

indexed integer laurent linear listlist logical mathfunc matrix moduledefinition odd monomial name negative nonnegative numeric point positive procedure radical

range rational relation RootOf rtable scalar SDMPolynom SERIES series set sqrt

string table taylor trig type uneval vector zppoly `!` `*` `+` `..` `.` `<=` `<>`

`<` `=` `and` `intersect` `minus` `module` `not` `or` `union` `^`

Большинство тестируемых вышеупомянутыми средствами Maple-языка типов относятся именно к первой группе – внешним типам. Типы, требующие для своего тестирования анализа (возможно рекурсивного) всего структурного дерева выражений, относятся ко второй группе – вложенным типам. В качестве вложенных Maple-язык рассматривает следующие 13 типов выражений, а именно:

algfun algnum appliable constant cubic expanded linear polynom quadratic quartic radfun radnum ratpoly

Все вышеперечисленные типы относятся к пакету Maple 10. Следует отметить, что константный тип относится ко второй группе, т.к. для его тестирования требуется анализ всего структурного дерева выражений на предмет отсутствия вхождения в него переменных компонент, т.е. х-компонент, для которых имеет место определяющее соотношение type(x,name) ⇒ true. В приводимых ниже примерах будут детализированы многие практические аспекты работы с типами при организации различных вычислительных конструкций, использующих функциональные средства Maple-языка.

По testeq-процедуре, имеющей формат кодирования testeq(A { {,|=} B}), производится стохастическое тестирование эквивалентности двух Maple-выражений либо эквивалентность заданного единственным фактическим аргументом выражения нулю. В случае установления факта эквивалентности возвращается true-значение, в противном случае – false-значение. При этом, false-значение является достоверным, тогда как значение true возвращается корректно с очень высокой степенью достоверности. В процессе тестирования процедура не только производит вычисления выражений-аргументов, но и их алгебраические преобразования и упрощения. При невозможности произвести тестирование возвращается FAIL-значение. Простой пример иллюстрирует вышесказанное:

> [testeq(sin(x)/cos(x), tan(x)), testeq(sin(x)^2 + cos(x)^2 - 1)]; ⇒ [true, true]

> [testeq(sqrt(-1) - I), testeq(x^2 + 4*x + 4, (x + 2)^2)]; ⇒ [true, true]

Весьма важной для задач формального анализа Maple-выражений представляется тестирующая match-процедура, имеющая следующий формат кодирования: match(<Выражение> = <Шаблон>, <Id>, '<Id_1>')

и возвращающая true-значение, если устанавливается соответствие структуры тестируемого, заданного первым аргументом, выражения указанному вторым аргументом функции шаблону по указанной ведущей Id-переменной. Шаблон представляет собой выражение по ведущей Id-переменной с формальными параметрами, на соответствие структуре которого и производится проверка выражения. Например, выражение z+a*x^2+b*x+c определяет шаблон квадратного трехчлена по ведущей х-переменной. В случае успешного тестирования в невычисленную Id_1-переменную помещается множество значений параметров шаблона, на которых он структурно эквивалентен тестируемому выражению. Следующий простой фрагмент иллюстрирует вышесказанное:

> [match(ln(Pi)/ln(x) + x^y = a/ln(x) + x^b, x, 'h'), h]; ⇒ [true, {b = y, a = ln(π)}] > [match(1942*sqrt(x) + 64^x - 10*y = a*x^d + b^x + c, x, 'h'), h];

[true, {a = 1942, d = 1/2, c = -10 y, b = 64}]

> [match(ln(z)*sqrt(x) + z^x - 10*y = a*x^b + c^x + d, x, ‘h’), h];

[true, {a = ln(z), c = z, d = -10 y, b = 1/2}]

> [match(ln(3*z/(1 + G(x)))*z + z^x - exp(y) = ln(a*z)*z + z^b + c, z, 'h'), h];

true, {c = −ey , a = 1 + G( )3 x , b = x} 

> [match(sqrt(3 + 32/G(x))*z + z^ln(x) - z/exp(y) + 10 = a*z + z^b + c, z, 'p'), p];

true, c = 10, a = − − 3 GG( )x( )ex + y 32 ey + 1, b = ln( )x  

Механизм шаблонов, поддерживаемый match-процедурой, позволяет устанавливать точную структуру Maple-выражений на основе заданного структурного шаблона с формальными параметрами, вычисляя значения последних (если установлено соответствие тестируемого выражения шаблону) по принципу функционального уравнения. Между тем, успешное применение match-процедуры для структурного тестирования выражений предполагает представление их в алгебраическом виде. В противном случае match-процедура может необоснованно возвращать false-значение, например:

> [match(19.89*sin(x) + x^57 - 10*y = a*sin(x) + x^b + c, x, 'h'), h]; ⇒ [false, h]

> [match(convert(19.89*sin(x) + x^57 - 10*y, 'rational') = a*sin(x) + x^b + c, x, 'h'), h];

true, {a = 1989 100 , c = −10 y, b = 57}  

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

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

! * + . < <= <> = @ @@ abstract_rootof algebraic algext algfun algnum algnumext And and anyfunc anyindex anything appliable

applied arctrig Array array assignable atomic attributed boolean BooleanOpt builtin cubic character ClosedIdeal CommAlgebra complex complexcons

composition const constant cx_infinity dependent dictionary dimension

disjcyc cx_zero filedesc embedded_axis embedded_imaginary embedded_real

equation even evenfunc xpanded extended_numeric extended_rational facint filename finite float float[] form fraction freeof function global hfarray

identical imaginary implies in indexable indexed indexedfun infinity integer

intersect last_name_eval laurent linear list listlist literal local logical mathfunc

Matrix matrix minus module moduledefinition monomial MonomialOrder

MVIndex name negative negint negzero neg_infinity NONNEGATIVE nonnegative nonnegint nonposint nonpositive nonreal Not not nothing numeric odd oddfunc operator Or or OreAlgebra package patfunc patindex patlist

Point point polynom posint positive poszero pos_infinity prime procedure property protected quadratic quartic Queue radalgfun radalgnum radext radfun radfunext radical radnum radnumext Range range rational ratpoly ratseq

realcons real_infinity relation RootOf rtable satisfies scalar SDMPolynom sequential SERIES series set sfloat ShortMonomialOrder SkewAlgebra

SkewParamAlgebra SkewPolynomial specfunc specified_rootof Stack specindex sqrt stack std stdlib string subset suffixed symbol SymbolicInfinity symmfunc table tabular taylor TEXT trig trigh truefalse type typefunc

typeindex undefined uneval union unit unit_name Vector vector verification verify xor with_unit zppoly ^ ||

Данные идентификаторы используются в качестве фактического значения второго аргумента {type|typematch}-функции либо возвращаются whattype-процедурой в результате тестирования Maple-выражений. Часть данных типов рассматривалась выше, многие из оставшихся типов будут рассмотрены ниже при обсуждении соответствующих вопросов Maple-языка. В представленном списке количество типов больше, чем в предыдущих релизах пакета и, как правило, с ростом номера релиза количество поддерживаемых им типов также растет.

В качестве типов, отличных от базовых (представляемых единым идентификатором, принадлежащим приведенному выше списку, например: integer, trig, list, set, float и др.), Maple-язык поддерживает структурные типы, формальный синтаксис которых может описывать основную структуру тестируемых выражений. Maple-язык допускает структурные типы двух видов: простые и функциональные. Простые структурные типы имеют следующий синтаксис: <Тип_1>#<Тип_2>, где два базовых типа соединены знаком оператора (#); в качестве основных язык для них допускает следующие: `=` `<>` `<` `<=` `>` `>=` `..` and or not &+ &* `^` `.`. А также: '<Тип>', [<Тип>], name[<Тип>] и fcntype, определяющие соответственно невычисленное выражение, список, индексированную ссылку указанного типа и функцию специального типа. В качестве функционального структурного типа Mapleязык допускает следующие основные синтаксически корректные конструкции:

set(<Тип>) – множество элементов заданного типа; list(<Тип>) – список элементов заданного типа;

`&+`(<Тип>) – сумма термов заданного типа;

`&*`(<Тип>) – произведение сомножителей заданного типа; identical(<Выражение>) – выражение, идентичное заданному; anyfunc(<Тип>) – произвольная функция заданного типа, кроме exprseq; function(<Тип>) – произвольная функция с аргументами заданного типа; specfunc(<Тип>, F) – функция F с аргументами заданного типа.

Наряду с рассмотренным выше механизмом шаблонов Maple-язык поддерживает и механизм типированных шаблонов, базирующийся на структурных типах, typematch-функции и (::)-операторе типизации. По (::)-оператору, имеющему максимальный приоритет, конструкция вида Id::<Тип> приписывает Id-идентификатору заданный тип, который может быть как базовым, так и структурным. Рассмотренная в общих чертах typematchфункция имеет формат кодирования следующего вида: typematch(<Выражение>, <Типированный шаблон> {, '<Id>'})

В рамках первых двух аргументов typematch-функция совпадает с type-функцией и возвращает true-значение в случае соответствия типированной структуры тестируемого выражения заданному вторым аргументом типированному шаблону, определяемому базовым либо структурным типом, иначе возвращается false-значение.

Третий же ее необязательный 'Id'-аргумент определяет невычисленную переменную и в сочетании с (::)-оператором типирования позволяет существенно расширять механизм типированных шаблонов. Суть расширения данного механизма сводится в общих чертах к следующему: в типированный шаблон, определяемый в качестве второго фактического аргумента typematch-функции, каждому типу посредством (::)-оператора приписывается некоторая переменная (типированная) и в случае возврата функцией true-значения в переменную, определяемую третьим фактическим аргументом, помещается список конкретных значений типированных переменных шаблона, на которых тестируемое выражение, определяемое первым фактическим аргументом функции, обеспечивает возврат true-значения. Следующий несложный фрагмент иллюстрирует ряд примеров применения тестирующих функций {type, typematch}:

> [typematch((99-42)..(99), a::integer..b::integer, 'h'), h]; ⇒ [true, [a = 57, b = 99]]

> [typematch(sin(x)^exp(x), a::trig^b::function, 'h'), h]; ⇒ [true, [a = sin(x), b = exp(x)]]

> type([sin(x), exp(y), ln(z)], list(function)); ⇒ true

> [typematch([sin(x),exp(y),ln(z)],list(L::function), 'h'), h];

[true, [L=sin(x), L=exp(y), L=ln(z)]]

> [typematch(sin(x)^19.99, b::trig^f::float, 'h'), h]; ⇒ [true, [b = sin(x), f = 19.99]]

> [typematch(x + y, `&+`(n::name, b::name), 'h'), h]; ⇒ [true, [n = x, b = y]]

> type(Art + Kr^(1/3) + 99,`&+`(name, radical, integer)); ⇒ true

> type({sin(x), cos(y), tan(z)}, set(trig)); ⇒ true

> [typematch({sin(x), cos(y), tan(z)}, set(L::trig), 'h'), h];

[true, [L=sin(x), L=cos(y), L=tan(z)]]

> type(F(57/180,47/99,10/89,32/67),function(rational)); ⇒ true

> [typematch(F(57/180, 47/99, 10/89, 3/96), f::function(G::rational), 'h'), h];

true,  G = 19 60, G = 47 99, G = 10 89, G = 321 , f = F 19 60, 47 99, 10 89, 321 

> [typematch(A||V||Z*abs(I^2), k::identical(AVZ), 'h'), h]; ⇒ [true, [k = AVZ]] > [typematch(F(x)*G(x) = 57, a::`*`=b::integer, 'h'), h];

[true, [a = F(x) G(x), b = 57]]

> [typematch(F(x) <> 19.99, a::function <> b::float, 'h'), h]; ⇒ [true, [a = F(x), b = 19.99]]

> typematch((Art + 17)*(Kr + 10),`&+`(p::name, h::odd)&*`&+`(t::name, s::even)); ⇒ true > [typematch((tan(x)*sin(x))^(64/59), (a::trig &* b::trig)^c::rational, 'h'), h];

true,  a = tan( )x , b = sin( )x , c = 64 59  

> [typematch(sin(x)*cos(x)^(V*G), a::trig &* b::trig^(c::name &* d::name), 'h'), h];

[true, [a = sin(x), b = cos(x), c = V, d = G]]

> typematch(Raadiku(64, 59, 39, 17, 10, 44, 95, 99), specfunc(integer, Raadiku)); ⇒ true

С учетом сказанного приведенный фрагмент достаточно прозрачен и каких-либо особых пояснений не требует. Вместе с тем, механизмы шаблонов, поддерживаемые Mapleязыком требуют для хорошего усвоения определенной наработки по их использованию, т. к. содержат целый ряд особенностей, здесь не рассматриваемых (см. прилож. 1 [12]).

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

Вместе с тем, для обеспечения структурного типированного анализа выражений весьма полезной представляется patmatch-процедура, имеющая формат кодирования: patmatch(<Выражение>, <Шаблон> {, 'h'})

где первый фактический аргумент определяет тестируемое выражение, а второй – тестирующий шаблон, т.е. шаблон, на соответствие которому проверяется исходное выражение.

Шаблон представляет собой типированное (::)-оператором алгебраическое выражение от ведущих переменных исходного выражения, на соответствие которому оно и проверяется. Процедура patmatch возвращает true-значение, если устанавливается структурно-типированное соответствие тестируемого выражения заданному шаблону, и false-значение в противном случае. В первом случае необязательной h-переменной присваивается список уравнений таких, что имеет место соотношение subs(h, <Шаблон>) = <Выражение>, во втором – h-переменная возвращается невычисленной (неопределенной). При этом, patmatch-процедура допускает использование и специального ключевого слова conditional, обеспечивающего возможность определения для шаблона дополнительных условий. Такие условия кодируются в одном из следующих двух форматах, а именно: conditional(<Шаблон>, <Условие>) и conditional(<Шаблон> = <B>, <Условие>)

В качестве условия выступают корректные булевские выражения, включающие типированные параметры шаблона, операторы отношения и логические операторы {and, or, not}. Вместе с тем, условие может включать и произвольные булевские функции и процедуры, возвращающие логические значения, например: isprime, issqr, type, typematch, match и даже patmatch, что позволяет производить рекурсивный структурно-типированный анализ Maple-выражений. Следующий фрагмент иллюстрирует применение процедуры patmatch для структурно-типированного анализа алгебраических выражений:

> patmatch(sqrt(17*Art + 10*Kr), sqrt(a::odd*Art + b::even*Kr), 'h'), h; true, [a = 17, b = 10]

> patmatch(10*x^3 + 3*y^2, a::even*x^b::prime + c::odd*y^d::even, 'h'), h; true, [a = 10, b = 3, c = 3, d = 2]

> patmatch(sin(3)*x + F(p*y + t*z), a::trig*x + F(b::symbol*y + c::name*z), 'h'), h; true, [a = sin(3), b = p, c = t]

> patmatch(sqrt(v)*x - ln(10)*Pi - exp(3), a::sqrt*x + b::atomic + c::realcons, 'h'), h; true, [a = v, c = −ln 10( ) π − e3 , b = 0]

> patmatch(sqrt(17*Art + 10*Kr), sqrt(a::even*Art + b::prime*Kr), 't'), t; ⇒ false, t

> patmatch(sqrt(17*Art + 10*Kr), conditional(sqrt(a::odd*Art + b::even*Kr), a< b^2), 'g'), g; true, [a = 17, b = 10]

> patmatch(10*Art + 3*Kr, conditional(a::even*Art + b::odd*Kr, a > b^2 and a + b <= 15 and evalf(a/b) > 3 and a*b < 32), 'h'), h; ⇒ true, [a = 10, b = 3]

> patmatch((10*A + 3*K)/(32*S - 52*G), conditional((10*A + b::odd*K)/(c::even*S - 52*G), c/b

> 10 and c + b >= 35 and b^3 < c), 'h'), h; ⇒ true, [b = 3, c = 32]

> patmatch(57*sin(x)*exp(y) + 52.47*ln(z), conditional(a::anything*exp(y) + b::float*ln(z), b

< 57 and _match(a = v*sin(x), x, 'p')), 'h'), h; ⇒ true, [a = 57 sin(x), b = 52.47]

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

Еще на одном функциональном средстве Maple-языка, использующем механизм шаблонов и табличную структуру данных, следует остановиться особо. По вызову процедуры compiletable([Шаблон_1 = Выход_1, …, Шаблон_n = Выход_n])

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

Тогда по вызову процедуры tablelook(<Выражение>, CT) возвращается выход СТ-таблицы, входу которого по шаблону соответствует указанное первым фактическим аргументом выражение. В случае отсутствия в СТ-таблице входа, по шаблону соответствующего выражению, процедурой возвращается FAIL-значение. Модифицировать СТ-таблицу путем добавления в нее новых входов можно посредством insertpattern-процедуры, по которой новые входы помещаются в конец таблицы. При необходимости помещения нового входа в другое место требуется новая компиляция СТ-таблицы. Следующий простой фрагмент иллюстрирует использование указанных средств Maple-языка для создания таблицы интегралов от простых выражений, содержащей только четыре входа (шаблоны подинтегральных выражений) и в качестве соответствующих им выходов – результаты интегрирования исходных шаблонов-выражений.

> T:=([a::algebraic*x::name^(n::integer) = a*x^(n+1)/(n+1), sin(x::name)*cos(x::name)^

(p::integer) = -cos(x)^(p+1)/(p+1), 1/(a::positive+b::positive*x::name^2) = arctan(b*x/

(sqrt(a*b)))/(sqrt(a*b)), sin(n::integer*x::name)^m::integer = int(sin(n*x)^m, x)]);

( ::a algebraic ) ( ::x name)( ::n integer) = a xn + (n + 11), T :=

(p + 1)

sin(x name:: ) cos(x name:: )( ::p integer) = − cos( )x ,

p + 1

2 = arctan  b a b x ,

1

( ::a positive) + ( ::b positive) ( ::x name) a b

( sin ( ::( n integer) ( ::x name)) =

m integer:: ) ⌡sin(n x)m dx 

> compiletable(T): map(tablelook, [10*y^3, sin(z)*cos(z)^3, 1/(1 + 2*h^2), sin(10*x)^3], %);

 5 2y 4 , −1 4 cos( )z 4 , 1 2 arctan( 2 h) 2, −301 sin 10( x)2 cos 10( x) − 151 cos 10( x) 

> map(whattype, [T, %%]); ⇒ [list, function]

Из представленного фрагмента достаточно прозрачен сам принцип создания функциональной СТ-таблицы и последующего ее использования. Рассмотренные средства пакета Maple обеспечивают простую возможность создания различного рода функциональных таблиц с параметрами и достаточно быстрого их просмотра. При этом, следует иметь в виду, что созданная таким образом функциональная таблица не является в строгом понимании Maple-языка структурой данных table-типа, как это иллюстрирует последний пример предыдущего фрагмента. Более детально читатель может ознакомиться с принципами организации функциональных таблиц в книгах [80,84,86-88].

При этом, следует отметить, что в определенной мере типировать идентификаторы можно и по assume-процедуре, как это иллюстрирует следующий простой пример:

> assume(A, integer); assume(B > 0); frac(A), sin(A*Pi), sqrt(-B); ⇒ 0 0, , B~ I

Аппарат шаблонов Maple-языка представляет собой довольно развитое уникальное средство, позволяющее проводить структурно-типированный анализ Maple-выражений, однако он не позволяет наделять конструкции языка требуемыми свойствами, подобно тому, как это делает подобный ему механизм математического пакета Mathematica. В деталях данный вопрос здесь не обсуждается и заинтересованный читатель отсылается к книгам [10-14,29,30,84].

Вместе с тем, (::)-оператор типирования поддерживает механизм автоматической проверки типов, передаваемых процедуре значений фактических аргументов, что при конкретном программировании используется весьма широко. В данном случае определяемые в процедуре формальные аргументы по (::)-оператору наделяются соответствующими типами (типируются), позволяя при вызове процедуры на фактических аргументах проверять их допустимость на заданные типы. В дальнейшем указанные средства тестирования типов будут широко использоваться в многочисленных иллюстративных примерах, детализируя их смысловую нагрузку. По конструкции ?{type|typematch|whattype} можно оперативно получать справочную информацию по {type, typematch, whattype} и список всех поддерживаемых Maple-языком типов. Ряд важных особенностей выполнения рассмотренных тестирующих функций можно найти в [12,91], тогда как с введенными нами новыми важными типами можно ознакомиться в [42,103,108,109].

В частности, нами был определен новый file-тип, поддерживаемый процедурой:

type/file := proc(F::anything) local a b c k f SveGal, , , , , ; global _datafilestate; if not type( ,F {'string', 'symbol'}) then return false else c := interface(warnlevel); null interface(( warnlevel = 0)) end if;

SveGal := proc( )f

try open ,(f 'READ'); close( )f catch "file or directory does not exist":

RETURN(false, unassign '( _datafilestate')) catch "file or directory, %1, does not exist":

RETURN(false, unassign '( _datafilestate')) catch "file I/O error" RETURN(: false, unassign '( _datafilestate')) catch "permission denied" RETURN(: false, unassign '( _datafilestate')) end try ;

true, assign67('_datafilestate' = 'close', f)

end proc ;

if Empty Red_n( ,( F " " 1, )) then null interface(( warnlevel = c)); ERROR "argument <%1> is invalid",( F)

else assign67(a = iostatus( ), b = NULL, f = CF(F)), null(interface(warnlevel = c))

end if;

if nops(a) = 3 then SveGal f( ), null interface(( warnlevel = c)) else for k in a[4 .. -1] do if CF(k[2]) = CF( )f then b := b, [k[1], k[2]] end if end do;

if b = NULL then SveGal f( ), null interface(( warnlevel = c))

else true, assign67('_datafilestate' = 'open b', ), null interface(( warnlevel = c))

end if

end if

end proc

Вызов type(K, file) возвращает true, если K – реальный файл, иначе false возвращается.

Глава 2. Базовые управляющие структуры Mapleязыка пакета

Для описания произвольного вычислительного алгоритма рассмотренных выше конструкций Maple-языка явно недостаточно по причине отсутствия средств по управлению вычислительным процессом. Глава и служит целям рассмотрения данных средств Maple.

2.1. Предварительные сведения общего характера

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

В задачу структурного кодирования входит получение корректной программы (модуля) на основе простых управляющих структур. В качестве таких базовых выбираются управляющие структуры следования, ветвления, организации циклов и вызовов функций (процедур, программ); при этом, все перечисленные структуры допускают только один вход и один выход. Более того, первые из трех указанных управляющих структур (следования, ветвления и организации циклов) составляют тот минимальный базис, на основе которого можно создавать любой сложности корректную программу с одним входом, одним выходом, без зацикливаний и недостижимых команд. Детальное обсуждение базисов управляющих структур программирования можно найти, в частности, в книгах [1-3] и в другой доступной литературе по основам программирования.

Следование отражает сам принцип последовательного выполнения предложений программы, пока не встретится изменяющее эту последовательность предложение. Например: Avz:=19.4; Ag:=47.52; Sv:=39*Av-6*Ag; Tal:=Art+Kr; - типичная управляющая структура следования, состоящая из последовательности четырех весьма простых Maple-предложений присваивания.

Ветвление определяет выбор одного из возможных путей дальнейших вычислений; типичными предложениями, обеспечивающими данную управляющую структуру, являются предложения типа «IF A THEN B ELSE C». Структура «цикл» реализует повторное выполнение группы предложений, пока выполняется некоторое логическое условие; типичными предложениями, обеспечивающими данную управляющую структуру, являются предложения: DO, DO_WHILE и DO_UNTIL. Таким образом, базисные структуры определяют соответственно последовательную (следование), условную (ветвление) и итеративную (цикл) передачи управления в программах. При этом, корректная структурированная программа теоретически любой сложности может быть написана с использованием только управляющих структур следования, IF-операторов ветвления и WHILEциклов. Однако расширение набора указанных средств, в первую очередь, за счет обеспечения вызовов функций и механизма процедур существенно облегчает программирование, не нарушая при этом структурированности программ и повышая уровень их модульности. При этом, сочетания (итерации, вложения) корректных структурированных программ, полученные на основе указанных управляющих структур, не нарушают их структурированности и корректности. Любых сложности и размера программы можно получать на основе соответствующего сочетания расширенного базиса (следование, ветвление, цикл, вызовы функций и механизм процедур) управляющих структур. Такой подход позволяет отказаться в программах от использования меток и безусловных переходов. Структура подобных программ четко прослеживается от начала (сверху) и до конца (вниз) при отсутствии передач управления на верхние уровни. Именно в свете сказанного Maple-язык представляет собой достаточно хороший пример лингвистического обеспечения при разработке эффективных структурированных программ, сочетающего лучшие традиции структурно-модульной технологии с ориентацией на математическую область приложений и массу программистски непрофессиональных пользователей из различных прикладных областей, включая и не совсем математической направленности. Для дальнейшего изложения напомним, что под «предложением» в Maple-языке понимается конструкция следующего простого вида:

<Maple-выражение> {;|:}

где в качестве выражения допускается любая корректная с точки зрения языка конструкция, например: A:= sqrt(60 + x): evalb(42 <> 64); sin(x) + x; `Tallinn-2006`:= 6; # Вызов и др. В рассмотренных ниже иллюстративных фрагментах приведено достаточно много различных примеров относительно несложных предложений в рамках управляющей структуры следования, которая достаточно прозрачна и особых пояснений не требует. Предложения кодируются друг за другом, каждое в отдельной строке или в одной строке несколько; завершаются в общем случае {;|:}-разделителями и выполняются строго последовательно, если управляющие структуры ветвления и цикла не определяют другого порядка. В дальнейшем предложения языка будем называть в соответствии с их определяющим назначением, например предложение присваивания, вызова функции, комментария, while-предложение, restart-предложение, if-предложение и т.д. Сделаем лишь одно замечание к предложению присваивания.

Наиболее употребительно определение предложения присваивания посредством одноименного (:=)-оператора, допускающего множественные присвоения. Однако, в целом ряде случаев вычислительные конструкции не допускают его использования. И в этом случае можно успешно использовать следующую процедуру Maple-языка: assign(Id{,|=} <Выражение>)

возвращающую NULL-значение (т.е. ничего) и присваивающую Id-идентификатору вычисленное выражение (которое, начиная с Maple 7, можеть быть и NULL). При этом, процедура assign в качестве единственного фактического аргумента допускает и список/множество уравнений вида Id = <Выражение>, определяющих соответствующие множественные присваивания. Например, списочная структура следующего фрагмента допустима лишь с использованием вышеупомянутой assign-процедуры пакета:

> [x:= 64, y:= 59*x, z:=evalf(sqrt(x^2 + y^2)), x + y + z]; Error, `:=` unexpected

> [assign(x= 64), assign(y, 59*x), assign(z, evalf(sqrt(x^2 + y^2))), x+y+z]; ⇒ [7616.542334] > [assign(x= 42), assign(y, 47*x), assign(z, evalf(sqrt(x^2 + y^2))), x + y + z]; Error, (in assign) invalid arguments

> [assign(['x'= 42, 'y'= 47*x, 'z'= evalf(sqrt(x^2 + y^2))]), x + y + z]; ⇒ [6250.639936]

Первый пример фрагмента иллюстрирует недопустимость использования (:=)-оператора, а три последующих - реализацию этой же списочной структуры на основе процедуры assign. При этом последний пример фрагмента демонстрирует использование невычисленных идентификаторов, обеспечивающих корректность вычислений. Тогда как 3-й пример иллюстрирует ошибочность повторного применения assign для переопределения вычисленных переменных. В дальнейшем assign-процедура широко используется в иллюстративных примерах, а в книге [103] представлен ряд полезных ее расширений.

2.2. Управляющие структуры ветвления Mapleязыка (if-предложение)

Достаточно сложные алгоритмы вычислений, обработки информации и/или управляющие (в первую очередь) не могут обойтись сугубо последовательной схемой, а включают различные конструкции, изменяющие последовательный порядок выполнения алгоритма в зависимости от наступления тех или иных условий: циклы, ветвления, условные и безусловные переходы (такие конструкции иногда называются управляющими). Для организации управляющих конструкций ветвящегося типа Maple-язык располагает довольно эффективным средством, обеспечиваемым if-предложением и имеющим следующие четыре формата кодирования:

(1) if <ЛУ> then <ПП> end if {;|:}

(2) if <ЛУ> then <ПП1> else <ПП2> end if {;|:}

(3) if <ЛУ1> then <ПП1> elif <ЛУ2> then <ПП2> else <ПП3> end if {;|:}

(4) `if`(<ЛУ>, V1, V2)

В качестве логического условия (ЛУ) всех четырех форматов if-предложения выступает любое допустимое булевское выражение, образованное на основе операторов отношения {<|<=|>| >=|=|<>}, логических операторов {and, or,not} и логических констант {true, false, FAIL}, и возвращающее логическое {true|false}-значение. Последовательность предложений (ПП) представляет собой управляющую структуру типа следования, предложения которой завершаются {;|:}-разделителем; для последнего предложения ПП кодирование разделителя необязательно. Во всех форматах, кроме последнего, ключевая фраза `end if` определяет закрывающую скобку (конец) if-предложения и его отсутствие идентифицирует синтаксическую ошибку, вид которой определяется контекстом if-предложения. Каждому if-слову должна строго соответствовать своя закрывающая скобка `end if`.

Первый формат if-предложения несет следующую смысловую нагрузку: если результат вычисления ЛУ возвращает true-значение, то выполняется указанная за ключевым thenсловом ПП, в противном случае выполняется следующее за if предложение, т.е. if-предложение эквивалентно пустому предложению. При этом, если if-предложение завершается (;)-разделителем, то выводятся результаты вычисления всех предложений, образующих ПП, независимо от типа завершающего их {:|;}-разделителя. Следовательно, во избежание вывода и возврата излишней промежуточной информации, завершать if-предложение рекомендуется (:)-разделителем.

Второй формат if-предложения несет следующую смысловую нагрузку: если результат вычисления ЛУ возвращает true-значение, то выполняется указанная за ключевым thenсловом ПП1, в противном случае выполняется следующая за ключевым else-словом ПП2. Замечание относительно вывода промежуточных результатов для случая первого формата if-предложения сохраняет силу и для второго формата кодирования.

ПП1, если evalb(ЛУ1) ⇒ true

ПП2, если (evalb(ЛУ1) ⇒ false)and(evalb(ЛУ2) ⇒ true)

R = ПП==========3, если (==========evalb(ЛУ1) ==========⇒ false)and==========(evalb(ЛУ2) ==========⇒ false)and==========(evalb(ЛУ3)======= ⇒ true)

ППn - 1, если (∀k |k ≤ n- 2)(evalb(ЛУk) ⇒ false)and(evalb(ЛУn - 1) ⇒ true)

ППn, in other cases

Третий формат if-предложения несет смысловую нагрузку, легко усматриваемую из общей R-функции, поясняющей принцип выбора выполняемой ППк в зависимости от цепочки истинности предшествующих ей ЛУj (j = 1 .. k) в предложении (см. выше).

А именно: if-предложение третьего формата возвращает R-результат выполнения ППк тогда и только тогда, когда справедливо следующее определяющее соотношение:

(∀j|j ≤ k-1) (evalb(ЛУj) ⇒ false) and (evalb(ЛУk) ⇒ true)

Данный формат if-предложения является наиболее общим и допускает любой уровень вложенности. Ключевое elif-слово является сокращением от “else if”, что позволяет не увеличивать число закрывающих скобок `end if` в случае вложенности if-предложения.

Наконец, по четвертому формату if-предложения возвращается результат вычисления V1-выражения, если evalb(<ЛУ>) ⇒ true, и V2-выражения в противном случае. Данный формат if-предложения подобно вызову Maple-функции можно использовать в любой конструкции, подобно обычному Maple-выражению, либо отдельным предложением. Следующий фрагмент иллюстрирует применение четырех форматов if-предложения:

> if (64 <> 59) then V:= 42: G:= 47: S:= evalf(sqrt(G - V), 6) end if: S; ⇒ 2.23607

> x:= 57: if type(x,'prime') then y:=x^2-99: z:=evalf(sqrt(y)) else NULL end if; y+z; ⇒ y + z > if (64 = 59) then V:= 42 else G:= 47: S:=evalf(sqrt(57 - G), 6) end if: [V, S]; ⇒ [42, 3.16228] > if (64 = 59) then V:= 42 elif (64 <= 59) then G:= 47 elif (39 >= 59) then S:= 67 elif (9 = 2) then Art:= 17 else `ArtVGS` end if; ⇒ ArtVGS

> AGN:= `if`(64 <> 59, 59, 64): Ar:= sqrt(621 + `if`(10 < 17, 2, z)^2): [AGN, Ar]; ⇒ [59, 25]

> if (64 = 59) then H:= `if`(3 <> 52, 52, 57) elif (57 <= 52) then H:= `if`(57 <> 52, 52, 57) elif

(32 >= 52) then S:= 67 elif (10 = 3) then Art:= 17 else `ArtVGS` end if; ⇒ ArtVGS

> V:= 64: G:= 59: if (V <> G) then S:= 39; if (V = 64) then Art:= 17; if (G = 59) then R:= G + V end if end if end if; ⇒ S := 39 > [%, S, Art, R]; ⇒ [123, 39, 17, 123]

> F:=x -> `if`(x < 39, S(x), `if`((39<=x) and (x<59), G(x), `if`((59<=x) and (x<64), V(x), Z(x)))): > [F(25), F(50), F(55), F(60), F(95), F(99)]; ⇒ [39, 59, 59, 64, Z(95), Z(99)]

Последний пример фрагмента иллюстрирует использование четвертого формата if-предложения для определения кусочно-определенной функции. Описание допустимых конструкций в качестве ЛУ и ПП для if-предложения обсуждалось выше, там же приведен целый ряд примеров по его применению. Ниже будет дана его дальнейшая детализация в контексте различного назначения иллюстративных фрагментов Maple-языка.

Синтаксис Maple-языка допускает также if-предложения следующего формата:

if <ЛУ1> then if < ЛУ2> then ... if <ЛУn> then <ПП> end if end if ... end if {;|:}

ЛУk - логические условия и ПП - последовательность предложений. Данного формата ifконструкция выполняется корректно, но результат возвращается без вывода. Его можно использовать по {%|%%|%%%}-предложению или по переменной, которой он был присвоен зараннее, как это иллюстрирует следующий простой фрагмент:

> V:=64: G:=59: if (V<>G) then if (V=64) then if (G=59) then R:= G+V end if end if end if;

> [%, R]; ⇒ [123, 123]

> if (V<>G) then S:=39; if (V=64) then Art:= 17; if (G=59) then R:=G+V end if end if end if;

S := 39

> [%, S, Art, R]; ⇒ [123, 39, 17, 123]

Последний пример фрагмента иллюстрирует существенно более широкую трактовку допустимости if-конструкций указанного формата и особенности их выполнения. Следует иметь в виду, что if-предложение возвращает {true|false}-значение даже в случае неопределенного по сути своей результата вычисления логического условия, например:

> [[a, b], `if`(eval(a) <> eval(b), true, false)]; ⇒ [[a, b], true]

> `if`(FAIL = FAIL, true, false); ⇒ true

С другой стороны, как иллюстрирует последний пример фрагмента, if-предложение отождествляет FAIL-значения, сводя различные неопределенности к единой, что также не соответствует здравому смыслу. Однако, причина этого лежит не в самом if-предложении, а в трактовке языком неопределенности, которая обсуждалась нами детально в [12].

Предложение if представляет собой наиболее типичное средство обеспечения ветвящихся алгоритмов. В этом контексте следует отметить, что аналогичное предложение Mathязыка пакета Mathematica [6,7] представляется нам существенно более выразительным, позволяя проще описывать ветвящиеся алгоритмы [33,39,41,42].

Наконец, Maple-язык допускает использование и безусловных переходов на основе встроенной функции goto, кодируемой в виде goto(<Метка>). Данная функция по вполне понятным причинам, обусловленным структурным подходом к программированию, недокументирована. Однако, в целом ряде случаев использование данного средства весьма эффективно, например, при необходимости погрузить в Maple-среду программу, использующую безусловные переходы на основе goto-предложения. Типичным примером являются Fortran-программы, широко распространенные в научных приложениях. Из нашего опыта следует отметить, что использование функции goto существенно упростило погружение в Maple целого комплекса физико-технических Fortran-программ, использующих goto-конструкции. Между тем, goto-функция имеет смысл только в теле процедуры, обеспечивая в точке вызова goto(<Метка>) переход к Maple-предложению, перед которым установлена указанная Метка. При этом, в качестве метки выступает некоторый идентификатор, как это иллюстрирует следующий простой фрагмент:

> A:= proc(x) `if`(x > 64, goto(L1), `if`(x < 59, goto(L2), goto(Fin)));

L1: return x^2;

L2: return 10*x + 17;

Fin: NULL end proc:

> A(350), A(39), A(64), A(10), A(17), A(64), A(59); ⇒ 122500, 407, 117, 187

При использовании вызовов goto(<Метка>) следует иметь в виду, что Метка является глобальной переменной, что предполагает ее выбор таким образом, чтобы она не пересекалась с переменными текущего сеанса, находящимися вне тела процедуры, использующей goto-переходы. Нами была определена процедура isplabel [41,103], использующая простой подход защиты goto-меток, который состоит в следующем. Если процедура использует, например, метки L1, L2, ..., Ln, то в начале исходного текста процедуры кодируется вызов unassign('L1', 'L2', ..., 'Ln'). Это позволяет обеспечивать ошибкоустойчивость всех меток процедуры. Однако данный подход требует определенной уверенности, что отмена значения L-метки не будет отрицательно сказываться на выполнении документа текущего сеанса, если он использует одноименную переменную L вне тела процедуры. Процедура uglobal [103] позволяет в Maple-процедуре работать с глобальными переменными, не заботясь о возможности нежелательных последствий такого решения на глобальном уровне текущего сеанса пакета. Детальнее вопросы использования goto-функции Maple-языка и средства, сопутствующие ее использованию, рассматриваются в рамках нашей Библиотеки [103]. Рассмотрев средства организации ветвления вычислительного алгоритма, переходим к средствам организации циклических конструкций в среде языка пакета Maple.

2.3. Циклические управляющие структуры Mapleязыка (while_do-предложение)

Рассмотрев средства организации ветвления вычислительного алгоритма, переходим к обсуждению средств организации циклических конструкций в среде Maple-языка. Циклическое предложение while_do служит для многократного вычисления заданного предложения или их последовательности по указанным его переменным (переменным цикла), принимающим определенное множество значений. При этом, предложение while_do имеет два основных формата кодирования, наиболее общие из которых имеют следующий принципиальный вид:

(1.a) for <ПЦ> in <Выражение> while <ЛУ> do <ПП> end do {;|:}

(1.b) for <ПЦ> in <Выражение> do <ПП> end do {;|:}

(1.c) in <Выражение> while <ЛУ> do <ПП> end do {;|:}

(1.d) in <Выражение> do <ПП> end do {;|:}

(1.e) do <ПП> end do {;|:}

(2.a) for <ПЦ> from <A> by <B> to <C> while <ЛУ> do <ПП> end do {;|:}

(2.b) for <ПЦ> from <A> to <C> while <ЛУ> do <ПП> end do {;|:}

(2.c) for <ПЦ> from <A> while <ЛУ> do <ПП> end do {;|:}

(2.d) for <ПЦ> while <ЛУ> do <ПП> end do {;|:}

(2.e) while <ЛУ> do <ПП> end do {;|:}

Наиболее общего вида вариант (1.а) первого формата while_do-предложения несет следующую смысловую нагрузку: для заданной переменной цикла (ПЦ), принимающей в качестве значений значения последовательных операндов указанного после in-слова выражения, циклически повторяется вычисление последовательности предложений (ПП), ограниченной скобками {do ….. end do}, до тех пор, пока логическое условие (ЛУ) возвращает true-значение (см. прилож. 3 [12]). В качестве выражения, как правило, выступают список, множество и диапазон, тогда как относительно ЛУ имеет место силу все сказанное для случая if-предложения. При этом, значения операндов, составляющих выражение, могут носить как числовой, так и символьный характер. Сказанное в адрес if-предложения относится и к типу используемого для предложения while_do завершающего его {;|:}-разделителя.

После завершения while_do-предложения управление получает следующее за ним предложение. Смысл вариантов (1.b..1.d) первого формата while_do-предложения с учетом сказанного достаточно прозрачен и особых пояснений не требует. Следующий простой фрагмент иллюстрирует принципы выполнения всех вариантов первого формата предложения while_do по организации циклических вычислений:

Первый формат while_do-предложения

> x:= 1: for k in {10, 17, 39, 44, 59, 64} while (x <=4 ) do printf(`%s%a, %s%a, %s%a|`, `k=`, k, `k^2=`, k^2, `k^3=`, k^3); x:= x + 1 end do:

k=10, k^2=100, k^3=1000|k=17, k^2=289, k^3=4913|k=39, k^2=1521, k^3=59319|k=44, k^2=1936, k^3=85184|

> for k in (h$h=1 .. 13) do printf(`%a|`, k^2) end do:

1|4|9|16|25|36|49|64|81|100|121|144|169|

> k:= 0: in (h$h=1 .. 99) while (not type(k, 'prime')) do k:=k+1: printf(`%a|`, k^2) end do:

1|4|

> do V:= 64: G:= 59: S:= 39: Ar:= 17: Kr:= 10: Arn:= 44: R:= V + G + S + Ar + Kr + Arn:

printf(`%s%a`, `R=`, R); break end do: R=233

> M:= {}: N:= {}: for k in [3, 10, 32, 37, 52, 57] do if type(k/4, 'odd') then break end if; M:= `union`(M, {k}); end do: M; # (1) ⇒ {3, 10, 32, 37}

> for k in [3, 10, 32, 37, 52, 57] do if type(k/2, 'odd') then next end if; N:= `union`(N, {k}); end do: N; # (2) ⇒ {3, 32, 37, 52, 57}

> T:= time(): K:=0: t:=10: do K:=K+1: z:= time() - T: if (z >= t) then break else next end if

end do: printf(`%s %a %s`, `Обработка =`, round(K/z), `оп/сек`); # (3) Обработка = 313587 оп/сек

> AG:=array(1..4,1..4): for k in a$a=1..4 do for j in a$a=1..4 do AG[k,j]:= k^j+j^k end do end do: AV:=copy(AG): for k in a$a=1..4 do for j in a$a=1..4 do if (k=j) then AG[k, j]:=0 else AG[k, j]:= k^j + j^k end if end do end do: print(AV, AG);

 2534 321783 1451754 4 145 512325 ,   04 35 173203 145170 4 145 3205



> restart; N:= {}: for h in [F(x), G(x), H(x), S(x), Art(x), Kr(x)] do N:= `union`(N, {R(h)}) end do: N; # (4) ⇒ {R(F(x)), R(G(x)), R(H(x)), R(S(x)), R(Art(x)), R(Kr(x))}

> map(R, {F(x), G(x), H(x), S(x), Art(x)}); ⇒ {R(F(x)), R(G(x)), R(H(x)), R(S(x)), R(Art(x))} > restart: for h in [F, G, H, S] do map(h, [x, y, z, t, u]) end do;

[F(x), F(y), F(z), F(t), F(u)]

[G(x), G(y), G(z), G(t), G(u)]

[H(x), H(y), H(z), H(t), H(u)]

[S(x), S(y), S(z), S(t), S(u)]

Второй формат while_do-предложения

> S:= {}: for k from 1 by 2 to infinity while (k <= 25) do S:= `union`(S, {k^3}) end do: S;

{1, 27, 125, 343, 729, 1331, 2197, 3375, 4913, 6859, 9261, 12167, 15625}

> S:= []: for k from 1 to infinity while (k <= 18) do S:= [op(S), k^3] end do: S;

[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728, 2197, 2744, 3375, 4096, 4913, 5832]

> S:= []: for k while (k <= 18) do S:= [op(S), k^3] end do: S;

[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728, 2197, 2744, 3375, 4096, 4913, 5832] > S:= []: p:= 0: while (p <= 10) do p:= p + 1: S:= [op(S), p^3] end do: S;

[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331]

> S:= [ ]: p:= 0: do p:= p + 1: if (p > 15) then break end if; S:= [op(S), p^3] end do: S;

[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728, 2197, 2744, 3375]

> h:= 0: for k from 19.42 by 0.001 to 20.06 do h:= h + k: end do: h; ⇒ 12653.340

> print("Прошу подготовить CD-ROM с АРМ - Вы располагаете одной мин.!"); T:= time(): do if time() - T >= 10 then break end if end do: print("Продолжение вычислений после ожидания:"); # (5)

"Прошу подготовить CD-ROM с АРМ - Вы располагаете одной мин.!"

"Продолжение вычислений после ожидания:"

Несколько пояснений следует сделать относительно варианта (1.e) первого формата, который в общем случае определяет бесконечный цикл, если среди ПП не определено программного выхода из него или не производится внешнего прерывания по stop-кнопке 3й строки GUI. В общем же случае для обеспечения выхода из циклической конструкции служит управляющее break-слово, в результате вычисления которого производится немедленный выход из содержащей его конструкции с передачей управления следующему за ней предложению. В случае вложенных циклов передача управления производится циклу, внешнему относительно break-содержащего цикла.

Тогда как по next-слову производится переход к вычислению циклической конструкции со следующим значением переменной цикла. Лучше всего различие управляющих значений break и next иллюстрируют простые примеры нижней части предыдущего фрагмента. Из них легко заметить, что если break-значение обеспечивает немедленный выход из цикла, в котором оно было вычислено, с передачей управления следующему за ним предложению илио внешнему относительно его циклу, то next-значение позволяет управлять счетчиком выполнения цикла, обеспечивая выбор очередного значения для переменной цикла без выполнения самого тела цикла, т.е. обеспечивается условное выполнение цикла. Так, в упомянутом выше фрагменте цикл, содержащий break-значение (1), завершается по мере встречи первого нечетного числа, тогда как для аналогичного цикла (2), содержащего next-значение, цикл выполняется полностью, но N-множество, в отличие от М-множества предыдущего цикла, формируется только из к-чисел списка, для которых значения к/2 являются четными. Наконец, пример (3) фрагмента иллюстрирует совместное использование для управления циклической конструкцией варианта (1.e) первого формата while_do-предложения управляющих значений next и break с целью оценки производительности ПК, работающего под управлением системы DOS+Windows + Maple 8, в единицах простых операций в с. Для обеспечения возможности последующего использования результатов выполнения циклических конструкций в их телах можно предусматривать аккумуляцию и сохранение результатов, получая возможность доступа к ним после завершения (даже в целом ряде случаев ошибочного или аварийного) выполнения циклической конструкции.

Наряду с численными первый формат while_do-предложения позволяет весьма успешно производить и символьные вычисления и обработку, так в частности, пример (4) предыдущего фрагмента иллюстрирует эквивалентную замену map-функции соответствующей циклической конструкцией и некоторые другие полезные преобразования.

Наиболее общего вида вариант (2.а) второго формата while_do-предложения несет следующую смысловую нагрузку: для заданной переменной цикла (ПЦ), принимающей в качестве значений последовательность значений A, A+B, A+2*B, ..., A+n*B ≤ C (где А,В,С - результаты вычисления соответствующих выражений), циклически повторяется вычисление последовательности предложений (ПП), ограниченной скобками {do….end do}, до тех пор, пока логическое условие (ЛУ) возвращает true-значение. В качестве А, В, С, как правило, выступают целочисленные выражения, тогда как относительно ЛУ имеет место силу все сказанное для случая if-предложения. Это же относится и к типу завершающего разделителя, используемого для предложения второго формата.

В случае отсутствия во втором формате while_do-предложения ключевых слов {from, by} для них по умолчанию полагаются единичные значения. Для С-выражения второго формата, стоящего за to-словом, допускается infinity-значение, однако в этом случае во избежание зацикливания (бесконечного цикла) необходимо определять условия завершения (или выхода из) цикла в ЛУ и/или в ПП. Минимально допустимым форматом while_doпредложения в Maple-языке является конструкция do ... end do {;|:}, определяющая собой пустой бесконечный цикл, на основе которого можно программировать не только различного рода программные задержки (как это показано в примере (5) последнего фрагмента), но и создавать различной сложности вычислительные конструкции вида do ... <ПП> end do, решающие циклического характера задачи с обеспеченным определенным выходом из них. В этом смысле (do...end do)-блок можно рассматривать в качестве тела любой циклической конструкции, управление режимом выполнения которой производится через управляющую оболочку типов for_from .... to_while и for_in_while.

Между тем, стандартные for_while_do-конструкции допускают только фиксированный уровень вложенности (вложенные циклы), тогда как в целом ряде приложений уровень вложенности определяется динамически в процессе выполнения алгоритма. В состав нашей Библиотеки [103] включена процедура FOR_DO, обеспечивающая работу с динамически генерируемыми конструкциями for_while_do любого конечного уровня вложенности.

Процедура FOR_DO обеспечивает динамическую генерацию «for_do»-конструкций следующих достаточно широко используемых типов, а именно:

(1) for k1 to M do Expr[k1] end do; [k1, M]

(2) for k2 by step to M do Expr[k2] end do; [k2, step, M]

(3) for k3 from a by step to M do Expr[k3] end do; [k3, a, step, M]

(4) for k4 in M do Expr[k4] end do; [k4, `in`, M]

Вызов процедуры FOR_DO принимает следующий вид (подробности см. в [41,103,109]):

FOR_DO([k1,M],[k2,step,M],[k3, `in`, M],[k4,a,step,M],…, [kp,…],"Expr[k1,k2,k3, …, kp]")

FOR_DO := proc() local k T E N n p R f, , , , , , , ;

`if`(nargs < 2, ERROR "quantity of actual arguments should be more than 1" `if`(( ),

{true} ≠ {seq(type(args[ ]k , 'list'), k = 1 .. nargs − 1)} or

not type(args -1[ ], 'string'), ERROR "actual arguments are invalid"( ), seq(

`if`(type(args[k], 'list') and type(args[k][1], 'symbol') and

member(nops args[ ]( k ), {2 3 4, , }), NULL,

ERROR "cycle parameters %1 are invalid",( args[ ]k )), k = 1 .. nargs − 1))); assign(N = "", n = 0, R = [ ], E = cat seq(( "end do; ", k = 1 .. nargs − 1)), f = cat(currentdir( ), "/$Art16_Kr9$" );)

T := table([2 = [`for`, `to`], 3 = [`for`, `by`, `to`], 4 = [`for`, `from`, `by`, `to`]]); for k to nargs − 1 do assign(' 'R = [op(R), cat "",( args[ ]k [1])]);

if member(`in`, args[ ]k ) then

N := cat(N, " for ", args[ ][k 1], " in " convert(, args[ ]k [3], 'string'), " ") else for p in T[nops(args[k])] do n := n + 1; N := cat(N p, , " ", args[ ][k n], " ")

end do

end if;

assign(' 'n = 0, ' 'N = cat(N, "do "))

end do;

writebytes ,(f cat(N, " " args[nargs] " ", , , E)), close( )f ; read f;

fremove( )f , unassign op(( map(convert R, , 'symbol'))) end proc

> ArtKr:= Array(1..3, 1..4, 1..5, 1..7, 1..14): FOR_DO([k,3], [j,2,4], [h,2,1,5], [p,2,3,7], [v,2,4,14], "ArtKr[k,j,h,p,v]:=k*j*h*p*v;"); interface(rtablesize = infinity); eval(ArtKr);

 1..3 x 1..4 x 1..5 x 1..7 x 1..14 5-D Array Data Type: anything Storage: rectangular Order: Fortran_order 

2.4. Специальные типы циклических управляющих структур Maple-языка пакета

Наряду с рассмотренными базовыми Maple-язык располагает рядом специальных управляющих структур циклического типа, позволяющих существенно упрощать решение целого ряда важных прикладных задач. Такие структуры реализуются посредством ряда встроенных функций и процедур {add, mul, seq, sum, product, map, member и др.}, а также $-оператора, позволяющих компактно описывать алгоритмы массовых задач обработки и вычислений. При этом, обеспечивается не только большая наглядность Mapleпрограмм, но и повышенная эффективность их выполнения. Следующий фрагмент иллюстрирует результаты вызова некоторых из перечисленных средств с эквивалентными им конструкциями Maple-языка, реализованными посредством базовых управляющих структур следования, ветвления и циклических.

> add(S(k), k=1..10); ⇒ S(1) + S(2) + S(3) + S(4) + S(5) + S(6) + S(7) + S(8) + S(9) + S(10)

> assign('A'=0); for k from 1 to 5 do A:=A+F(k) end do: A; ⇒ F(1) + F(2) + F(3) + F(4) + F(5)

> mul(F(k), k=1..13); ⇒ F(1) F(2) F(3) F(4) F(5) F(6) F(7) F(8) F(9) F(10) F(11) F(12) F(13)

> assign('M'=1); for k from 1 to 7 do M:=M*F(k) end do: M; ⇒ F(1) F(2) F(3) F(4) F(5) F(6) F(7)

> seq(F(k), k=1..12); ⇒ F(1), F(2), F(3), F(4), F(5), F(6), F(7), F(8), F(9), F(10), F(11), F(12)

> assign('S'=[]); for k from 1 to 5 do S:=[op(S),F(k)] end do:op(S); ⇒ F(1), F(2), F(3), F(4), F(5)

> map(F, [x1,x2,x3,x4,x5,x6,x7,x8]); ⇒ [F(x1), F(x2), F(x3), F(x4), F(x5), F(x6), F(x7), F(x8)]

> assign('m'=[]); for k in [x1, x2, x3, x4, x5, x6, x7, x8, x9, x10] do m:=[op(m),F(k)] end do: m;

[F(x1), F(x2), F(x3), F(x4), F(x5), F(x6), F(x7), F(x8), F(x9), F(x10)]

> L:= [x1, x2, x3, x4, x5, x6, x7, x8, x9, x10]: member(x3, L); ⇒ true

> for k to nops(L) do if L[k]=x10 then R:=true: break else R:=false end if end do: R; ⇒ true

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

> G:= [59, 64, 39, 44, 10, 17]: add(G[k]*`if`(type(G[k], 'odd'), 1, 0), k=1..nops(G)); ⇒ 115

> mul(G[k]*`if`(type(G[k], 'even'), 1, `if`(G[k]=0, 1, 1/G[k])), k=1..nops(G)); ⇒ 28160

> F:=[10,GS,17]: (seq(F[k]*`if`(type(F[k],‘symbol’),1,0),k=1..nops(F)))(x,y,z); ⇒ 0, GS(x,y,z), 0 > (seq(`if`(type(F[k], 'symbol'), true, false) and F[k], k=1..nops(F))); ⇒ false, GS, false

Первые два примера фрагмента иллюстрируют применение управляющей if-структуры для обеспечения дифференцировки выбора слагаемых и сомножителей внутри функций add и mul. Тогда как третий и четвертый примеры формируют последовательности вызовов функций на основе результатов их тестирования. Данные приемы могут оказаться достаточно полезным средством в практическом программировании в среде языка Maple различного рода циклических вычислительных конструкций.

В циклических конструкциях типа `for k in n$n=a..b...` не допускается отождествления идентификаторов k и n, не распознаваемого синтаксически, но приводящего к ошибкам выполнения конструкции. Более того, в общем случае нельзя отождествлять в единой конструкции переменные цикла и суммирования/произведения, например:

> h:= 0: for k to 180 do h:= h + sum(1, k = 1 .. 64) end do; h; ⇒ 0

Error, (in sum) summation variable previously assigned, second argument evaluates to 1 = 1 .. 64

> h:= 0: for k to 180 do h:= h + sum(1, 'k' = 1 .. 64) end do: h; ⇒ 11520

> h:= 0: for k to 180 do h:= h + product(1, k = 1 .. 64) end do: h; ⇒ 0

Error, (in product) product variable previously assigned, second argument evaluates to 1 = 1 .. 64 > h:= 0: for k to 180 do h:= h + product(2, 'k' = 1 .. 64) end do: h;

3320413933267719290880

> h:= 0: for k in ['k' $ 'k'=1..64] do h:= h + k end do: h; ⇒ 2080

> h:= 0: for k in [k $ 'k'=1 .. 64] do h:= h + k end do: h; ⇒ 4096

> h:= 0: for k in [k $ k=1 .. 64] do h:= h + k end do: h; ⇒ 0

Error, wrong number (or type) of parameters in function $

Вместе с тем, как иллюстрирует фрагмент, корректность выполняется при кодировании переменной суммирования/произведения в невычисленном формате. Более того, три последних примера фрагмента иллюстрируют как допустимость, так и корректность использования общей переменной внешнего цикла и циклической $-конструкции, но при условии использования последней в невычисленном формате. Данное обстоятельство определяется соглашениями Maple-языка по использованию глобальных и локальных переменных, детально рассматриваемых в следующей главе книги, посвященной процедурным объектам языка.

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

> S:="aqwertyuopsdfghjkzxc": R:=convert(S,'bytes'): convert([R[k]],'bytes') $ k=1..nops(R); Error, byte list must contain only integers

> cat(seq(convert([R[k]], 'bytes'), k = 1 .. nops(R))); ⇒ "aqwertyuopsdfghjkzxc"

> X,Y:=99,95: seq(H(k),k=`if`(X<90,42,95)..`if`(Y>89,99,99)); ⇒ H(95),H(96),H(97),H(98),H(99)

Таким образом, оба, на первый взгляд, эквивалентные средства формирования последовательностных структур следует применять весьма осмотрительно, по возможности отдавая предпочтение первому, как наиболее универсальному. В этом отношении для seqфункции имеют место (в ряде случаев весьма полезные) следующие соотношения: seq(A(k), k = [B(x)]) ≡ seq(A(k), k = {B(x)}) ≡ (A@B)(x) ≡ A(B(x)) seq(A(k), k = x) ≡ seq(A(k), k = B(x)) ≡ A(x)

При использовании `if`-функции для организации выхода из циклических конструкций рекомендуется проявлять внимательность, ибо возвращаемое функцией break-значение не воспринимается в качестве управляющего слова Maple-языка пакета, например:

> R:= 3: do R:= R - 1; if R= 0 then break end if end do: R; ⇒ 0

> R:= 3: do R:= R-1; `if`(R=0, `break`, NULL) end do: R; ⇒ 0

Error, invalid expression

Первый пример фрагмента иллюстрирует успешный выход из do-цикла по достижении R-переменной нулевого значения и удовлетворения логического условия if-предложения. Тогда как второй пример показывает невозможность выхода из идентичного do-цикла на основе `if`-функции, возвращающей на значении R=0 break-значение, не воспринимаемое в качестве управляющего слова. При этом, если в Maple 7–10 инициируется ошибочная ситуация, то еще в Maple 6 второй пример, не вызывая ошибочной ситуации, выполняет бесконечный цикл, требуя прекращения вычислений по stop-кнопке GUI. В случае использования вместо break функций done, quit и stop выполняется бесконечный цикл, требуя прекращения вычислений по stop-кнопке GUI.

Циклические вычислительные конструкции можно определять и на основе функций {select, remove}, имеющих следующий единый формат кодирования:

{select|remove}(<ЛФ>, <Выражение> {, <Параметры>})

Результатом вызова select-функции является объект того же типа, что и ее второй фактический аргумент, но содержащий только те операнды выражения, на которых логическая функция (ЛФ) возвращает true-значение. Третий необязательный аргумент функции определяет дополнительные параметры, передаваемые ЛФ. В качестве выражения могут выступать список, множество, сумма, произведение либо произвольная функция. Функция remove является обратной к select-функции. Следующий простой фрагмент иллюстрирует применение функций select и remove для циклических вычислений:

> select(issqr, [seq(k, k= 1 .. 350)]);

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324]

> ЛФ:= x ->`if`(x >= 42 and x <= 99, true, false): select(ЛФ, [seq(k, k = 1 .. 64)]);

[42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64]

> remove(ЛФ, [seq(k, k = 1 .. 64)]); ⇒ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41]

Приведенный фрагмент достаточно прозрачен и особых пояснений не требует.

В целом ряде случаев работы с данными индексированного типа {array, Array и др.} возникает необходимость динамической генерации вложенных циклических конструкций, когда уровень циклической вложенности заранее неизвестен и вычисляется программно. В этом случае может оказаться весьма полезной процедура SEQ [41,103]. Процедура обеспечивает динамическую генерацию seq-конструкций следующего типа:

seq(expr[k], k = a .. b) и seq(expr[k], k = V)

Следующий пример иллюстрирует применение SEQ-процедуры:

> SG:= Array(1..2, 1..3, 1..4): SEQ([k, 1..2], [j, {1, 2, 3}], [h, 2..3], "assign('SG'[k,j,h]=k+j+h)"); ArrayElems(SG);

{(1, 1, 2) = 4, (1, 1, 3) = 5, (1, 2, 2) = 5, (1, 2, 3) = 6, (1, 3, 2) = 6, (1, 3, 3) = 7, (2, 1, 2) = 5,

(2, 1, 3) = 6, (2, 2, 2) = 6, (2, 2, 3) = 7, (2, 3, 2) = 7, (2, 3, 3) = 8}

Используемый процедурой прием может быть использован для расширения других циклических конструкций [41,103]. Детальнее группа функциональных средств, объединенных управляющей структурой циклического типа, на семантическом уровне рассматривалась выше. Здесь же на нее было акцентировано внимание именно в связи с вопросом механизма организации управляющих структур Maple-языка.

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

Глава 3. Организация механизма процедур в Maple-языке пакета

Сложность в общем случае является достаточно интуитивно-субъективным понятием и его исследование представляет весьма трудную фундаментальную проблему современного естествознания да и познания вообще. Поэтому его использование ниже носит интуитивный характер и будет основываться на субъективных представлениях читателя. За свою историю человечество создала немало весьма сложных проектов и систем в различных областях, к числу которых с полным основанием можно отнести и современные вычислительные системы (ВС) с разрабатываемым для них программным обеспечением (ПО). Поэтому обеспечение высокого качества разрабатываемых сложных программных проектов представляется не только чрезвычайно важной, но и весьма трудной задачей, носящей многоаспектный характер. В последнее время данному аспекту программной индуствии уделяется особое внимание.

Решение данной задачи можно обеспечивать двумя основными путями: (1) исчерпывающее тестирование готового программного средства (ПС), устранение всех ошибок и оптимизация его по заданным критериям; и (2) обеспечение высокого качества на всех этапах разработки ПС. Так как для большинства достаточно сложных ПС первый подход неприемлим, то наиболее реальным является второй, при котором вся задача разбивается на отдельные объекты (модули), имеющие хорошо обозримые структуру и функции, относительно небольшие размеры и сложность и структурно-функциональное объединение (композиция) которых позволяет решать исходную задачу. При таком модульном подходе сложность ПС редуцируется к существенно меньшей сложности составляющих его компонент, каждая из которых выполняет четкие функции, обеспечивающие в совокупности с другими компонентами требуемое функционирование ПС в целом. Метод программирования, когда вся программа разбивается на группы модулей, каждый со своей контролируемой структурой, четкими функциями и хорошо определенным интерфейсом с внешней средой, называется модульным программированием.

Поскольку модульный является единственной альтернативой монолитного (в виде единой программы) подхода, то вопрос состоит не в целесообразности разбивать или нет большую программу на модули, а в том - каков должен быть критерий такого разбиения. На сегодня практика программирования знает и использует целый ряд методов организации многомодульных ПС, когда разбиение на модули основывается на их объемных характеристиках в строках исходного текста, выделении однотипных операций и т.д. Однако наиболее развитым представляется критерий, в основе которого лежит хорошо известный принцип «черного ящика». Данный подход предполагает на стадии проектирования ПС представлять его в виде совокупности функционально связанных модулей, каждый из которых реализует одну из допустимых функций. При этом, способ взаимодействия модулей должен в максимально возможной степени скрывать принципы его функционирования и организации. Подобная модульная организация приводит к выделению модулей, которые характеризуются легко воспринимаемой структурой и могут проектироваться и разрабатываться различными проектировщиками и программистами. Более важный аспект состоит в том, что многие требуемые модификации сводятся к изменению алгоритмов функционирования отдельных модулей без изменения общей структурно-функциональной организации ПС в целом. Вопросы современной концепции модульного программирования базируются на ряде основных предпосылок, рассматриваемых, например, в книгах [1-3] и в цитируемой в них весьма обширной литературе.

3.1. Определения процедур в Maple-языке и их типы

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

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

Процедура в среде Maple-языка имеет следующую принципиальную структуру: proc(<Последовательность формальных аргументов>){::Тип;} local <Последовательность идентификаторов>; Описательная global <Последовательность идентификаторов>; часть options <Последовательность параметров>; определения uses <Последовательность имен пакетных модулей>;

description <Описание>; процедуры

<Т Е Л О процедуры>

end proc {;|:}

Заголовок процедуры содержит ключевое proc-слово со скобками, в которых кодируется последовательность формальных аргументов процедуры; данная последовательность может быть и пустой, т.е. минимально допустимый заголовок процедуры имеет вид proc( ). Позади заголовка может кодироваться тип, относящийся к типу возвращаемого процедурой результата. Подробнее об этом говорится ниже. Ключевые слова local, global и options определяют необязательные описательные секции процедуры, представляющие соответственно последовательности идентификаторов локальных, глобальных переменных и параметров (опций) процедуры. Необязательная description-секция содержит последовательность строк, описывающих процедуру. Если данная секция представлена в определении процедуры, то она будет присутствовать и при выводе последней на печать. Практически все библиотечные процедуры пакета содержат description-секцию, хотя она и не выводится на печать. При этом, если данная секция содержит несколько предложений, то она должна кодироваться последней, иначе инициируется ошибочная ситуация, как это иллюстрирует нижеследующий простой фрагмент:

> P:= proc() description "Сумма аргументов"; `+`(args); `+`(args) end proc: print(P); proc() description "Сумма аргументов"; `+`(args); `+`(args) end proc

> P(42, 47, 67), op(5, eval(P)); ⇒ 156, "Сумма аргументов"

> P:= proc() description "Сумма аргументов"; `+`(args); `+`(args); option trace; end proc; Error, reserved word `option` or `options` unexpected

В Maple 10 дополнительно к указанным определение процедуры допускает использование необязательной uses-секции, определяющей имена пакетных модулей, используемых процедурой. Например, кодирование секции «uses LinearAlgebra» эквивалентно использованию в процедуре use-предложения следующего формата: use LinearAlgebra in < ... > end use;

Данная секция не идентифицируется при выводе либо возврате определения процедуры, исчезая подобно use-предложению, как это иллюстрирует следующий пример: > P:= proc() local a,b,c; global d; uses LinearAlgebra; option remember; description "grsu"; BODY end proc;

P := proc() local a, b, c; global d; option remember; description "grsu"; BODY end proc И не возвращается ее содержимое по вызовам op(k, eval(P)) (k=1..8) подобно случая других секций описания произвольной процедуры Р.

На наш взгляд, секция uses носит непринципиальный характер, в определенной мере лишь упрощая (сокращая) оформление процедуры, тогда как с точки зрения читабельности исходного текста использование в теле use-предложений даже более предпочтительно. Не говоря уже о том, что данное непринципиальное новшество ведет к несовместимости процедур «сверху-вниз» относительно релизов пакета.

Секции local, global, options, uses и description составляют описательную часть определения Maple-процедуры, которая в общем случае может и отсутствовать. Управлять выводом тела процедуры можно посредством установки переменной verboseproc interfaceпроцедуры Maple-языка. Минимальной конструкцией, распознаваемой ядром в качестве процедуры, является структура следующего весьма простого вида, а именно: Proc := proc() end proc: whattype(eval(Proc)); ⇒ procedure

Распознаваемый пакетом Proc-объект в качестве процедуры, между тем, особого смысла не имеет, ибо вызов процедуры Proc(args) на любом кортеже фактических аргументов всегда возвращает NULL-значение, т.е. ничего. Для возможности вызова процедуры ее определение присваивается некоторому Id-идентификатору Id := proc({ |Args}) ... end proc {:|;}, позволяя после его вычисления производить вызов процедуры по Id({|Args })конструкции с заменой ее формальных Args-аргументов на фактические Args -аргументы. Наряду с классическим определением именованной процедуры Maple-язык допускает использование и непоименованных процедур, определения которых не присваиваются какому-либо идентификатору. Для вызова непоименованных процедур служит конструкция следующего вида:

proc({ |Args}) ... end proc(Args )

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

> proc() [nargs, args] end proc(42, 47, 67, 89, 96, -2, 95, 99); ⇒ [8, 42, 47, 67, 89, 96, -2, 95, 99]

> proc(n) sum(args[k], k=2..nargs)^n end proc(3, x, y, z, a, b, c, h); ⇒ (x+y+z+a+b+c+h)3

> D(proc(y) (y^9 + 3*y^5 - 99)/6*(3*y^2 - 256) end proc)(9); ⇒ 2648753541

> a*proc(x) x^2 end proc(42) + b*proc(y) y^2 end proc(47); ⇒ 1764 a + 2209 b

> D(proc(y) (y^9 + 2*y^5 - 99)/13*(3*y^2 - 256) end proc);

proc(y) 1/13*(9*y^8+10*y^4)*(3*y^2-256)+6/13*(y^9+2*y^5-99)*y end proc

> restart: (D@@9)(proc(x) G(x) end proc); ⇒ proc(x) (D@@9)(G)(x) end proc

Непоименованные процедуры можно использовать в ряде выражений и обрабатывать некоторыми функциями и операторами, как это иллюстрируют примеры фрагмента. В этом смысле наиболее часто непоименованные процедуры используются в конъюнкции с функциями {map,map2}, а также с другими функциями и операторами, допускающими неалгебраические выражения в качестве своих фактических аргументов и операндов. В то же время, как правило, рекомендуется использовать именно именованные Maple-процедуры, что позволяет исключать различного рода ошибки.

Тело процедуры содержит текст описания алгоритма решаемой ею задачи с использованием рассмотренных выше средств Maple-языка, а именно: данных и их структур допустимых типов, переменных, функций пакетных, модульных и пользовательских, других процедур и т. д., логически связанных управляющими структурами следования, ветвления и цикла, рассмотренными выше. Данное описание должно удовлетворять правилам допускаемого языком синтаксиса. Завершается процедура кодированием закрывающей процедурной скобки `end proc`. Представленная структура является определением процедуры, которое активизируется только после его вычисления. При этом, во время вычисления процедуры не производится выполнения предложений тела процедуры, но интерпретатор Maple-языка производит все возможные упрощения тела процедуры, являющиеся простым типом ее оптимизации. Реальное же выполнение тела процедуры производится только в момент ее вызова с передачей ей фактических аргументов, значения которых замещают все вхождения в тело формальных аргументов. Завершается определение процедуры обязательным предложением `end proc {:|;}` (закрывающей скобкой).

Вторым способом определения процедур является использование функционального оператора (->), позволяющего представлять процедуры в нотации классической операции отображния, а именно в следующем простом виде:

(Args) -> <Выражение от Args-переменных>

При этом, в случае единственного аргумента кодирование его в скобках не обязательно. Присвоение данной конструкции идентификатору позволяет определить процедуру, например: > G:= (x, y) -> evalf(sqrt(x^2 + y^2)): G(42, 47); ⇒ 63.03173804. Последовательность формальных аргументов в таким образом определяемой процедуре может быть пустой, а ее тело должно быть единым выражением либо if-предложением языка:

> W:= () -> `if`(member(float, map(whattype, {args})), print([nargs, [args]]), NULL):

> W(42, 47, 19.98, 67, 96, 89, 10,3, `TRG`); ⇒ [9, [42, 47, 19.98, 67, 96, 89, 10, 3, TRG]] > H:= x -> if (x < 42) then 10 elif (x >= 42)and(x <= 99) then 17 else infinity end if:

> map(H, [39, 49, 62, 67, 95, 98, 99, 39, 17, 10, 39]); ⇒ [10, 17, 17, 17, 17, 17, 17, 10, 10, 10, 10]

> [D(x -> sin(x))(x), (x -> sqrt(x^3 + 600))(10)]; ⇒ [cos(x), 40]

> map((n) -> `if`(type(n/4, 'integer'), n, NULL), [seq(k, k= 42..64)]) ⇒ [44, 48, 52, 56, 60, 64] > Z:= () -> if sum(args[k], k= 1 .. nargs) > 9 then args else false end if:

> [Z(3, 6, 8, 34, 12, 99), Z(3, -6, 8, -21, 6, 10)]; ⇒ [3, 6, 8, 34, 12, 99, false]

При этом, следует иметь в виду, что второй способ определения предназначен, прежде всего, для простых однострочных процедур и функций, ибо не поддерживает механизма локальных и глобальных переменных, а также опций. Между тем, в целом ряде случаев он оказывается весьма полезным приемом программирования. Подобно первому способу определения процедур, второй также допускает использование непоименованных процедур, используемых в тех же случаях, что и первый способ. Как это иллюстрируют последние примеры предыдущего фрагмента (см. прилож. 3 [12]).

Наконец, третий способ определения однострочных простых процедур базируется на использовании define-процедуры, имеющей для этого формат кодирования вида: define(F, F(x1::<Тип_1>, ..., xn::<Тип_n>) = <Выражение>(x1, ..., xn))

По этому формату define-процедура в качестве F-процедуры/функции от типированных (x1, ..., xn)-аргументов/переменных определяет выражение от этих же ведущих переменных. При этом, допускается использование не только типов, идентифицируемых typeфункцией, но и структурных типов. Фактические аргументы, определяемые их типированными формальными аргументами, могут принимать любые совместимые с указанными для них типами значения. Успешный вызов процедуры define возвращает NULL-значение. Вызов определенной таким образом процедуры/функции производится по конструкции F(x1,..., xn). Следующий фрагмент иллюстрирует применение define-процедуры для определения пользовательских процедур:

> define(G, G(x::integer, y::anything, z::fraction) = sqrt(x^3 + y^3 + z^3));

> interface(verboseproc = 3): eval(G); proc() local theArgs arg look me cf term, , , , , ;

option `Copyright (c) 1999 Waterloo Maple Inc. All rights reserved.`; description "a Maple procedure automatically generated by define()"; me := eval procname, 1 ;( )

theArgs := args;

look := tablelook '( procname'(theArgs), '[`/POS` 1,( G, 3),

`/BIND` 1, 1,( `/x1` integer:: ), `/BIND` 2, 1,( `/x2` anything:: ), `/BIND` 3, 1,( `/x3` fraction:: ),

`/PATTERN` (( `/x1`^3 + `/x2`^3 + `/x3`^3)^(1/2))]'); if look ≠ FAIL then eval(look, `/FUNCNAME` = procname) else 'procname'(theArgs)

end if

end proc

> [G(42, 64., 19/99), G(42, 64., 1999), G(42., 64, 350/65), G(`REA`, `13.09.06`)];

579.8551604 G(, 42 64. 1999, , ), G 42., 64,  70 13 , G(REA 13.09.06, )  

> define(S, S(x, y::string, z::integer) = cat(x, "", y, "", z)); S(RANS, " - 13 сентября ", 2006);

S(RANS, " - 13 сентября ", 2006)

> proc(x, y::string, z::integer) cat(x, "", y, "", z) end (RANS, " - 13 сентября ", 2006);

RANS - 13 сентября 2006

> S1:= (x, y, z) -> cat(x, "", y, "", z): S1(RANS, " - 13 сентября ", 2006);

RANS - 13 сентября 2006

Второй пример фрагмента иллюстрирует вид исходного текста процедуры G, генерируемой пакетом по define-процедуре. В случае передачи определенной по define процедуре некорректных фактических аргументов (несоответствие числа фактических аргументов формальным и/или их типов) ее вызов возвращается невычисленным, как это иллюстрирует третий пример фрагмента. При этом, если определенная первым способом процедура допускает корректное выполнение в случае превышения числа фактических аргументов над формальными (игнорируя лишние), то в случае определенной третьим способом данная ситуация полагается ошибочной, возвращая вызов невычисленным. Напоминая второй способ определения процедур, третий отличается от него существенным моментом, позволяя использовать типизацию формальных аргументов. Между тем, третий способ определения процедур имеет определенные ограничения, не позволяя использовать в теле процедуры произвольные Maple-функции. Так, третий пример фрагмента иллюстрирует некорректность вызова определенной третьим способом S-процедуры (в ее теле использована cat-функция), тогда как определения эквивалентных ей процедур первым и вторым способом возвращают вполне корректные результаты. Следовательно, третий способ следует использовать весьма осмотрительно.

Подобным третьему способом определения пользовательской функции является применение специальной unapply-процедуры, имеющей следующий формат кодирования: unapply(<Выражение> {, <Ведущие переменные>})

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

> F:= unapply((gamma*x^2 + sqrt(x*y*z) + x*sin(y))/(Pi*y^2 + ln(x + z) + tan(y*z)), x, y, z); γ x2 + x y z + x sin( )y

F := (x y z, , ) → 2 + ln(x + z) + tan(y z) π y

> evalf(F(6.4, 5.9, 3.9)), F(m, n, p), evalf(F(Pi/2, Pi/4, Pi));

γ m2 + m n p + m sin(n)

0.2946229694 , n 2 + ln(m + p ) + tan(n p ), 1.674847463 π

> W:=[unapply(sin(x),x), unapply(x,x), unapply(x^3,x)]; W(6.4);

W := [sin, x -> x, x -> x3 ]

[0.1165492049, 6.4, 262.144]

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

При необходимости определения функции с неопределенным числом формальных аргументов в общем случае второй аргумент unapply-процедуры (последовательность ведущих переменных) может не кодироваться, как это иллюстрирует следующий фрагмент:

> SV:= unapply(evalf([nargs, sqrt(sum(args['k']^2, 'k' = 1..nargs))/nargs], 6));

SV ( ) → nargs, nargsk∑nargs = 1 argsk2  :=

> [6*SV(64,39,59,44,10,17), evalf(SV(64,39,59,44,10,17), 6)]; ⇒ [[36, 11423 ], [6. 17.8130, ]]

> VS:= () -> evalf([nargs, sqrt(sum(args['k']^2, 'k' = 1..nargs))/nargs], 6);

nargs, ' 'nargsk∑nargs = 1 args' 'k 2 , 6

VS := ( ) → evalf

> k:= 'k': [VS(64,39,59,44,10,17), evalf(VS(64,39,59,44,10,17), 6)]; ⇒ [6., 17.8130], [6., 17.8130]]

Первый пример фрагмента представляет SV-функцию от неопределенного числа формальных аргументов, определенную на основе unapply-процедуры, а второй - VS-функцию, определенную на основе функционального (->)-оператора и эквивалентную (по реализованному алгоритму) SV-функции. Вместе с тем, как иллюстрируют примеры фрагмента, между обоими определениями имеется существенное различие, а именно: игнорируется ряд встроенных функций (evalf, convert) при определении SV-функции. Поэтому, в общем случае способ определения функции на основе функционального (->)-оператора является более универсальным.

Следует отметить, что представленный способ определения пользовательской функции на основе define-процедуры по целому ряду характеристик предпочтительнее способа, базирующегося на функциональном (->)-операторе или unapply-процедуре, как это иллюстрирует следующий весьма поучительный фрагмент:

> R:=[42,47,67,62,89,96]: S:=[64,59,39,44,17,10]: define(V,V(x::integer)=interp(R,S,x)); V(95);

11

> map(GS, [42, 47, 67, 62, 89, 96]); ⇒ [64, 59, 39, 44, 17, 10]

> define(H, H(x::anything, y::integer) = sqrt(x^2 + y^2));

> H(sqrt(75), 5), evala(H(sqrt(75), 5)), H(sqrt(75), 59.47); ⇒ 100 10 H(, , 5 3 59.47, )

> x:= [a, b, c, d, e, f]: define(DD, DD(x[k]::prime$'k'=1..6) = (sqrt(sum(x[k]^2, 'k'=1..6)))); > evala(DD(3, 1999, 71, 13, 17, 7)), evala(DD(64, 59, 39, 44, 17, 10));

4001558 DD(, 64 59 39 44 17 10, , , , , )

> restart; define(R, R(x::even, y::odd) = sqrt(x^2 + y^2)), R(10, 17)^2; ⇒ 389

> define(R, R(x::even, y::odd) = sqrt(x^3 + y^3)), R(10, 17);

Error, (in DefineTools:-define) R is assigned

> R:= 'R': define(R, R(x::even, y::odd) = sqrt(x^3 + y^3)), R(10, 17)^2; ⇒ 5913

> define(AG, AG(0)=1, AG(1)=2, AG(2)=3, AG(t::integer) = AG(t-3) + 2*AG(t-2) + AG(t-1));

> map(AG, [10, 17, 15, 18, 20]); ⇒ [1596, 336608, 72962, 723000, 3335539]

> define(G3, G3(seq(x||k::integer, k=1 .. 6)) = sum(args[k], k=1 .. nargs)/nargs); > 3*G3(10, 17, 39, 44, 95, 99); ⇒ 152

> define(G4, G4()=sum(args[k], 'k'=1..nargs)/nargs): 3*G4(10, 17, 39, 44, 95, 99); ⇒ 152 > define(G6, G6() = [nargs, args]):

Error, (in insertpattern) Wrong kind of arguments

> G7:= () -> [nargs, args]: G7(10, 17, 39, 44, 95, 99); ⇒ [6, 10, 17, 39, 44, 95, 99]

> define(G8, G8() = args), define(G9, G9() = nargs), G8(10, 17), G9(10, 17, 39, 44); ⇒ 10, 4

> define(S1, define(S2, S2()= sum(args[k], 'k'=1..nargs)), S1() = S2(V, G, S)*nargs);

> S1(10, 17, 39, 44, 95, 99), S2(10, 17, 39, 44, 95, 99); ⇒ 6 V + 6 G + 6 S, 304

Фрагмент иллюстрирует реакцию пакета на переопределения функции, определенной на основе define-подхода, и вызовы пользовательской H-функции от двух типированных аргументов. На примере H-функции проиллюстрированы следующие два весьма принципиальных момент, а именно:

(1) результат вызова define-определенной функции в общем случае требует последующей обработки evala-функцией для получения окончательного результата;

(2) вызов define-определенной функции на фактических аргументах, не отвечающих оп- ределенным для них типам, возвращается невычисленным.

Использование последовательности свойств позволило определить целочисленную рекуррентную AG-функцию. Пример определения G3-функции иллюстрирует возможность использования специальных переменных args, nargs в define-определении функции, тогда как пример G4-функции дополнительно иллюстрирует определение функции от неопределенного числа аргументов. Однако, при этом следует иметь в виду, что использование указанных переменных в define-определении функций носит более ограниченный характер, чем при других способах определения функций пользователя. Более того, как показывают примеры определения двух эквивалентных функций G6 и G7, в плане представимости типов выражений, используемых при определении функции, функциональный (->)-оператор более предпочтителен. Примеры определения функций G8 и G9 также иллюстрируют ограничения define-способа задания функций. В этом отношении следует отметить, что реализация define-функции более младших релизов Maple во многих отношениях была более эффективной [8-10]. Наконец, последний пример фрагмента иллюстрирует (в ряде случаев весьма полезную) возможность рекурсивного использования функции define для определения функций пользователя.

Использование assign-процедуры для присвоения определения функции некоторому идентификатору (ее имени) позволяет включать определения функций непосредственно в вычислительные конструкции, соблюдая только одно правило: вычисление определения функции должно предшествовать ее первому вызову в вычисляемом выражении. Сказанное относится к любому способу определения функции, например:

> assign(G1, unapply([nargs, sum(args['k'], 'k'=1..nargs)]));

> assign(G2, () -> [nargs, sum(args['k'], 'k'=1..nargs)]);

> define(G3, G3()= nargs*sum(args[k], 'k'=1..nargs)), G1(42,47,67,62,89,96),

G2(42,47,67,62,89,96), G3(42,47,67,62,89,96); ⇒ [6, 403], [6, 403], 2418

> assign(Kr, unapply([nargs, sum(args[p], 'p'=1..nargs)])), Kr(10, 17); ⇒ [2, 27]

В частности, последний пример фрагмента иллюстрирует вычисление списочной структуры, содержащей unapply-определение Kr-функции, с последующим ее вызовом. Следует отметить при этом, что механизм пользовательских функций, поддерживаемый пакетом Mathemaica [6, 7], представляется нам существенно более гибким при реализации алгоритмов обработки.

При этом следует иметь ввиду, что в случае определения процедуры в assign-конструкции ее определение становится глобальным, если ее имя не определено в local-секции содержащей конструкцию процедуры, как это иллюстрирует следующий фрагмент:

> P:=proc() local Proc; assign(Proc = (() -> `+`(args))); Proc(args)/nargs end proc;

P := proc() local Proc; assign(Proc = (() -> `+`(args))); Proc(args)/nargs end proc > P(42,47,67), eval(Proc);

52, Proc

> restart; P:=proc() assign(Proc = (() -> `+`(args))); Proc(args)/nargs end proc;

P := proc() assign(Proc = (() -> `+`(args))); Proc(args)/nargs end proc > P(42,47,67), eval(Proc);

52, () -> `+`(args)

Еще об одном ухищрении стоит сказать особо. В релизах 6 и ниже по конструкции вида m(assign('a' = <Выражение>), где m – произволное целое либо NULL

можно было широко использовать assign-конструкции в выражениях, ибо возвращалось значение m с вычислением выражений в скобках, например:

> m:=10: (m(assign('a'=2006)) + a - 16)/(6(assign('b'=1995)) + b - 1); ⇒ 1 # Maple 6

Тогда как, начиная с релиза 7, данная возможность была исключена, например:

> m:=10: (m(assign('a'=2006)) + a - 16)/(6(assign('b'=1995)) + b - 1); # Maple 7 – 10 6 + a

5 + b

Что породило еще один тип несовместимости релизов пакета «снизу-вверх» для тех, кто пытался использовать особенности Maple-языка при программировании своих задач.

Рассмотрев способы определения процедур в Maple-языке и их структурную организацию, обсудим более детально отдельные компоненты структуры процедур, определяемых первым способом, как наиболее универсальным и часто используемым. Это тем более актуально, что позволит вам не только понимать реализацию пакетных процедур и процедур нашей Библиотеки [41] (некоторые из них представлены и в настоящей книге), но и самому приступать к созданию собственных конкретных приложений в среде Maple, реализованных в форме процедур и их библиотек.

3.2. Формальные и фактические аргументы Mapleпроцедуры

Формальный аргумент процедуры в общем случае имеет вид <Id>::<Тип>, т. е. Id-идентификатор с приписанным ему типом, который не является обязательным. В случае определения типированного формального аргумента при передаче процедуре в момент ее вызова фактического аргумента, последний проверяется на соответствие типу формального аргумента. При несовпадении типов идентифицируется ошибочная ситуация с возвратом соответствующей диагностики. Совершенно иная ситуация имеет место при несовпадении числа передаваемых процедуре фактических аргументов числу ее формальных аргументов: (1) в случае числа фактических аргументов, меньшего определенного

для процедуры числа формальных аргументов, как правило, идентифицируется ошибочная ситуация типа “Error, (in Proc) Proc uses a nth argument <Id>, which is missing”, указывающая на то, что Proc-процедуре было передано меньшее число фактических аргументов, чем имеется формальных аргументов в ее определении; где Id - идентификатор первого недостающего n-го фактического аргумента; (2) в случае числа фактических аргументов, большего определенного для процедуры числа формальных аргументов, ошибочной ситуации не идентифицируется и лишние аргументы игнорируются. Между тем, и в первом случае возможен корректный вызов. Это будет в том случае, когда в теле процедуры не используются формальные аргументы явно. Следующий простой фрагмент хорошо иллюстрирует вышесказанное:

> Proc:=proc(a, b, c) nargs, [args] end proc: Proc(5, 6, 7, 8, 9, 10), Proc(5), Proc(), Proc(5, 6, 7);

6, [5, 6, 7, 8, 9, 10], 1, [5], 0, [], 3, [5, 6, 7]

> Proc:= proc(a, b, c) a*b*c end proc: Proc(5, 6, 7), Proc(5, 6, 7, 8, 9, 10); ⇒ 210, 210 > Proc(645);

Error, (in Proc) Proc uses a 2nd argument, b, which is missing

> AVZ:= proc(x::integer, y, z::float) evalf(sqrt(x^3 + y^3)/(x^2 + y^2)*z) end proc:

> AVZ(20.06, 59, 64);

Error, AVZ expects its 1st argument, x, to be of type integer, but received 20.06

> AVZ(2006, 456);

Error, (in AVZ) AVZ uses a 3rd argument, z (of type float), which is missing

> [AVZ(64, 42, 19.42), AVZ(59, 42, 19.42, 78, 52)]; ⇒ [1.921636024, 1.957352295]

В момент вызова процедуры с передачей ей фактических выражений для ее соответствующих формальных аргументов первые предварительно вычисляются и их значения передаются в тело процедуры для замещения соответствующих им формальных аргументов, после чего производится вычисление составляющих тело Maple-предложений с возвратом значения последнего вычисленного предложения, если не было указано противного. В случае наличия в определении процедуры типированных формальных аргументов элементы последовательности передаваемых при ее вызове фактических значений проверяются на указанный тип и в случае несовпадения инициируется ошибочная ситуация, в противном случае выполнение процедуры продолжается. В качестве типов формальных аргументов процедуры используются любые из допустимых языком и тестируемых функцией type и процедурой whattype. При использования нетипированного формального аргумента рекомендуется все же указывать для него anything-тип, информируя других пользователей процедуры о том, что для данного формального аргумента допускаются значения любого типа, например, кодированием заголовка процедуры в виде proc(X::integer, Y::anything).

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

> A:=proc(a::integer, b::float) a*b end proc: A(64, 42);

Error, invalid input: A expects its 2nd argument, b, to be of type float, but received 42

> lasterror;

"invalid input: %1 expects its %-2 argument, %3, to be of type %4, but received %5"

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

> A1:=proc(a::numeric, b::numeric) local a1, b1; assign(a1=a, b1=b); if not type(a, 'integer') then a1:=round(a) end if; if not type(b, 'float') then b1:=float(b) end if; a*b end proc: A1(64, 42), 17*A1(10/17, 59); ⇒ 2688, 590

Большинство процедур нашей Библиотеки [103] использует именно подобный программный подход к обработке получаемых фактических аргументов на их допустимость и, по возможности, производятся допустимые корректировки. Это существенно повышает устойчивость процедур относительно некорректных фактических аргументов.

При организации процедур роль типированных формальных аргументов не ограничивается только задачами проверки входной информации, но несет и ряд других важных нагрузок. В частности, использование uneval-типа для формального аргумента позволяет вне процедуры проводить его модификацию (т.е. обновлять на «месте» Maple-объект, определенный вне тела процедуры под этим идентификатором) как это иллюстрирует следующий фрагмент:

> A:= proc(L::list, a::anything) assign('L' = subs(a = NULL, L)) end proc:

> L:= [64, 59, 39, 44, 10, 17]; A(L, 64);

L := [64, 59, 39, 44, 10, 17]

Error, (in assign) invalid arguments

A1 := proc(L::uneval, a::anything) if not type( ,L 'symbol') then error "1st argument should be symbol but had received %1"whattype(, L)

elif type(eval(L), {'list', 'set'}) then assign ' '( L = subs(

[`if`( not type( ,a {'list', 'set'}), a = NULL, seq(k = NULL, k = a))], eval(L) )) else error "1st argument should has type {list, set} but had received %1-type", whattype eval(( L))

end if

end proc

> A1(L, 64), L, A1(L, 59), L, A1(L, {59, 39, 44, 10, 17}), L;

[59, 39, 44, 10, 17], [39, 44, 10, 17], []

> A1(AVZ, 64), AVZ;

Error, (in A1) 1st argument must has type {list, set} but had received symbol-type > A1([1, 2, 3, 4, 5, 6], 64);

Error, (in A1) 1st argument must be symbol but had received list

Попытка определить такую операцию для стандартно типированного L-аргумента в Апроцедуре вызывает ошибку выполнения, тогда как, определив этот же L-аргумент как аргумент uneval-типа и использовав в дальнейшем обращение к нему через eval-функцию, получаем вполне корректную А1-процедуру, обеспечивающую обновление «на месте» списка/множества L путем удаления его элементов, определенных вторым a-аргументом, в качестве которого может выступать как отдельный элемент, так и их список/ множество. Проверка же на тип фактического L-аргумента производится уже программно в самой процедуре; при этом, проверяется не только на тип {list, set}, но и на получение идентификатора объекта, а не его значения (т.е. в качестве фактического L-аргумента должно выступать имя списка/множества). Данный прием может оказаться весьма полезным в практическом программировании, именно он используется рядом процедур нашей Библиотеки [41,103,108,109].

Начиная с Maple 10, кроме типирования формальных аргументов пакет допускает определение для позиционных аргументов значений по умолчанию. Кодируется это посредством оператора присвоения в формате <аргумент> := <значение>, например:

> P:= proc(a, b, c:=sin(x), d) (a+b)/(c+d) end proc;

Error, optional positional parameters must follow any required positional parameters > P:= proc(a, b, c:=sin(x), d:=cos(x)) (a+b)/(c+d) end proc: > P(a, b), P(10, 17, 5), P(10, 17, 5, 2);

a + b 27 27

, , sin( )x + cos( )x 5 + cos( )x 7

> P1:= proc(a, b, c, d) (a+b)/`if`(nargs = 2, (sin(x) + cos(x)), `if`(nargs = 3, (c+cos(x)), (c+d))) end proc:

> P1(a, b), P(10, 17, 5), P(10, 17, 5, 2);

a + b 27 27

, , sin( )x + cos( )x 5 + cos( )x 7

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

> P:=proc(f::symbol, x::symbol, {b:= evalf}, c::range(posint)) b(int(f(x), x=c)) end proc;

P := proc(f::symbol, x::symbol, c::range(posint), {b := evalf}) b(int(f(x), x = c)) end proc

> P(sin, y, 1..5, b=G), P(sin, y, 1..5, G), P(sin, y, 1..5, evalf), P(sin, y, 1..5, b);

G(cos(1) - cos(5)), 0.2566401204, 0.2566401204, true(cos(1) - cos(5))

Из него, в частности, следует, что использование в вызове процедуры такого аргумента в виде уравнения либо значения по умолчанию приводит к требуемым результатам, тогда как вызов только на левой части вместо фактического аргумента производит подстановку вместо фактического аргумента true-значения. В определенной мере данный механизм и его расширения в ряде случаев оказываются довольно полезными, однако вызывают несовместимость с более ранними релизами. Между тем, этот механизм в целом ряде случаев несложно реализуется программно и прежними средствами Maple-языка. Например, второй пример предпоследнего фрагмента может быть реализован способом, представленным последним примером фрагмента. Естественно, при большем количестве позиционных параметров программирование усложняется и представленный механизм более эффективен, одно ограничение – его действие ограничивается последними формальными аргументами.

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

Между тем, пользователь и сам может создавать весьма эффективные системы обработки аргументов процедуры, как позиционных, так и ключевых со значениями для них по умолчанию; один из таких простых подходов представляет следующий фрагмент:

P := proc(x, y, z) if type(eval( )y , 'symbol') then return procname( ,x 2006, )z end if; x + + y z end proc

> P(a, b, c), P(a, x-y, c), P(sin(x), h, cos(y)); ⇒ a + 2006 + c, a + x - y + c, sin(x) + 2006 + cos(y)

Читателю рекомендуется разобраться с используемым практически полезным приемом.

Для организации процедуры наряду с предложениями, описывающими непосредственный алгоритм решаемой задачи (а в ряде случаев и для обеспечения самого алгоритма), язык Maple предоставляет ряд важных средств, обеспечивающих функции, управляющие выполнением процедуры. Прежде всего, к ним можно отнести переменные args и nargs, возвращающие соответственно последовательность переданных процедуре фактических аргументов и их количество. Оба эти средства имеют смысл только в рамках процедуры, а по конструкциям вида args{|[n]|[n..m]} можно получать {последовательность фактических аргументов|n-й аргумент| аргументы с n-го по m-й включительно} соответственно. Тогда как nargs-переменная возвращает количество полученных процедурой фактических аргументов. Назначение данных средств достаточно прозрачно и обусловливает целый ряд их важных приложений при разработке пользовательских процедур. В первую очередь, это относится к обработке получаемых процедурой фактических аргументов. В частности, nargs-переменная необходима с целью обеспечения определенности выполнения вычислений в случае передачи процедуре неопределенного числа аргументов. Следующий простой фрагмент иллюстрирует вышесказанное:

> SV:= proc() product(args[k], k= 1 .. nargs)/sum(args[k], k= 1 .. nargs) end proc: > 137*SV(42, 47, 62, 67, 89, 96, 350, 39, 44, 59, 64);

22698342960272179200

> GN:= proc() [nargs, [args]] end proc: GN(V, G, S, A, Art, Kr);

[6, [V, G, S, A, Art, Kr]]

> map(whattype, [59, 17/10, ln(x), 9.9, "RANS"]); ⇒ [integer, fraction, function, float, string] > Arg_Type:= proc() map(whattype, [seq(args[k], k= 1 .. nargs)]) end proc:

> Arg_Type(59, 17/10, ln(x), 9.9, "RANS"); ⇒ [integer, fraction, function, float, string] > Arg_Type:= proc() map(whattype, [args[k]$k= 1 .. nargs]) end proc:

> Arg_Type(59, 17/10, ln(x), 9.9, "RANS"); ⇒ [integer, fraction, function, float, string]

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

proc() <ТЕЛО> {Ψ(args[1], args[2], ..., args[n])|n = nargs} end proc {;|:} что оказывается весьма удобным механизмом для организации процедур, ориентированных, в первую очередь, на задачи символьных вычислений и обработки [9-14,39].

Дополнительно к переменным args и nargs можно отметить еще одну важную переменную procname, возвращающую имя процедуры, ее содержащей. В целом ряде случаев данная переменная оказывается весьма полезной, в частности, при возвращении вызова процедуры невычисленным. С этой целью используется конструкция простого формата 'procname(args)'. Многие пакетные процедуры возвращают результат именно в таком виде, если не могут решить задачу. Ряд процедур и нашей Библиотеки [103] поступают аналогичным образом. Между тем, переменная procname может использоваться и в других полезных приложениях. Следующий фрагмент иллюстрирует применение указанной переменной как для организации возврата вызова процедуры невычисленным, так и для вывода соответствующего сообщения:

AVZ := proc() if nargs ≤ 6 then

WARNING "%1(%2) = %3", procname,( seqstr args( ), `+` args nargs( )/ ) else 'procname args( )'

end if

end proc

> AVZ(64, 59, 39, 44, 10, 17);

Warning, AVZ(64, 59, 39, 44, 10, 17)=233/6

> AVZ(64, 59, 39, 44, 10, 17, 6); ⇒ AVZ(64, 59, 39, 44, 10, 17, 6)

Процедура AVZ при получении не более 6 фактических аргументов выводит соответствующее сообщение, описывающее вызов процедуры и его результат, тогда как в противном случае вызов процедуры возвращается невычисленным.

Наконец, для проверки допустимости передаваемых процедуре необязательных аргументов служит процедура DefOpt(args), где args – передаваемые аргументы [41-43,103].

DefOpt := proc() local `0` `1` `2` `3`, , , ;

[assign('`0`' = [unassign '( `2`', '`3`')], '`1`' = [ ],

'`3`' = (( ) → {seq(lhs([args] 1[ ][`2`]), `2` = 1 .. nops(args 1[ ]))})), seq(`if`( type(args[`2`], 'equation') and type(lhs args[( `2`]), 'symbol'), assign('`0`' = [op(`0`), lhs(args[`2`]) = [op(rhs(args[`2`])), 'makelist']]), `if`( type(args[`2`], 'equation') and type(lhs args[( `2`]), 'posint'), assign('`1`' = [op(`1`), args[`2`]]), ERROR "invalid arguments %1", [( args]))), `2` = 1 .. nargs) `if`, (nops(`1`) < `3` `1`( )[-1] or nops(`1`) ≠ nops(`3` `1`( )),

ERROR "invalid positional values %1",( `1`), `if`(nops(`0`) ≠ nops(`3` `0`( )),

ERROR("invalid options values %1", `0`),

TABLE [( 'OptionParms' = TABLE(`0`), 'PositionalParms' = TABLE(`1`)])))] end proc

Исходные тексты процедур Библиотеки, прилагаемой к книгам [41,103] и к настоящей, предоставляют хороший иллюстративный материал по использованию аргументов, переменных процедуры args, nargs и procname для разработки различных приложений.

3.3. Локальные и глобальные переменные Mapleпроцедуры

Используемые в теле процедуры переменные по области определения делятся на две группы: глобальные (global) и локальные (local). Глобальные переменные определены в рамках всего текущего сеанса и их значения доступны как для использования, так и для модификации в любой момент и в любой Maple-конструкции, где их применение корректно. Для указания переменной глобальной ее идентификатор кодируется в global-секции определения процедуры, обеспечивая процедуре доступ к данной переменной. В этой связи во избежание возможной рассинхронизации вычислений и возникновения ошибочных ситуаций рекомендуется в качестве глобальных использовать в процедурах только те переменные, значения которых ими не модифицируются, а только считываются. Иначе не исключено возникновение отмеченных ситуаций, включая и непредсказуемые. Следующий пример иллюстрирует некорректность определения в процедуре глобальной хпеременной:

> x:= 64: proc(y) global x; x:= 0; y^(x+y) end proc(10); evalf(2006/x); ⇒ 10000000000 Error, numeric exception: division by zero

> x:= 64: proc(y) global x; x:= 0; y^(x+y) end proc: evalf(2006/x); ⇒ 31.34375000

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

Локальные переменные также можно типировать подобно аргументам, но действие этого типирования имеет смысл лишь при установке kernelopts(assertlevel=2), например:

> P:= proc(a, b) local c::integer; c:=a+b end proc: P(42.47,6); ⇒ 48.47

> kernelopts(assertlevel=2): P(42.47,6);

Error, (in P) assertion failed in assignment, expected integer, got 48.47

Если для переменных, используемых в определении процедуры, не определена область их действия (local, global), то Maple-язык классифицирует их следующим образом. Каждая переменная, получающая в теле процедуры определение по (:=)-оператору либо переменная цикла, определяемая функциями {seq, add, mul} полагается локальной (local), остальные полагаются глобальными (global) переменными. При этом, если переменные for-цикла не определены локальными явно, то выводится предупреждающее сообщение вида "Warning, `k` is implicitly declared local to procedure `P`”, где k и P – переменная цикла и имя процедуры соответственно. Тогда как уже для функций sum и product переменные цикла рассматриваются глобальными, не выводя каких-либо сообщений, что предполагает их явное определение в local-секции. Однако вне зависимости от наличия предупреждающих сообщений рекомендуется явно указывать локальные и глобальные переменные, что позволит не только избегать ошибок выполнения, но и более четко воспринимать исходный текст процедуры. Следующий фрагмент иллюстрирует вышесказанное:

> G:=2: A:=proc(n) V:=64: [args, assign('G', 5), assign('V', 9), assign(cat(H, n), `h`)] end proc: Warning, `V` is implicitly declared local to procedure `A`

> [A(99), G, V, A(10), whattype(H9), H9]; ⇒ [[99], 5, 9, [10], symbol, H9]

> k:= 64: H:= proc() product(args[k], k=1 .. nargs)/sum(args[k], k=1 .. nargs) end proc: > [k, H(42, 47, 62, 67, 96, 89, 10, 17, 4), k]; Error, (in H) invalid subscript selector

> k:=64: P:= () -> [seq(args[k], k=1..nargs)]: P(1, 2, 3), k; ⇒ [1, 2, 3], 64

> k:=64: P:= () -> [sum(args[k], k=1..nargs)]: P(1, 2, 3), k; Error, (in P) invalid subscript selector

> k:=64: P:= () -> [product(args[k], k=1..nargs)]: P(1, 2, 3), k; Error, (in P) invalid subscript selector

> k:=64: P:=proc() for k to nargs do end do end proc: P(1, 2, 3), k; ⇒ 64

Warning, `k` is implicitly declared local to procedure `P`

Таким образом, в указанных случаях соответствующие переменные процедуры при ее вычислении неявно декларируются локальными с выводом или без предупреждающих сообщений. С другой стороны, глобальные переменные даже без их явного декларирования в global-секции можно генерировать в рамках процедуры, как это иллюстрирует 1й пример предыдущего фрагмента. Делать это позволяет процедура assign. Однако работа с такими глобальными переменными чревата непредсказуемыми последствиями. Таким образом, практика программирования в среде Maple-языка рекомендует следовать следующим двум правилам определения области действия переменных:

(1) глобальными определять переменные, лишь используемые в режиме ”чтения”;

(2) локальные переменные определять явно в local-секции процедуры.

Использование данных правил позволит избежать многих ошибок, возникающих лишь в момент выполнения Maple-программ, синтаксически и семантически корректных, но не учитывающих специфики механизма использования языком глобальных и локальных переменных. А именно: если глобальная переменная имеет областью определения весь текущий сеанс, включая тело процедуры (глобально переопределять ее можно внутри любой Maple-конструкции), то локальная переменная областью определения имеет лишь тело самой процедуры и вне процедуры она полагается неопределенной, если до того не была определена вне процедуры глобально переменная с тем же идентификатором. Данный механизм имеет довольно глубокий смысл, ибо позволяет локализовать действия переменных рамками процедуры (в общем случае черного ящика), не влияя на общий вычислительный процесс текущего сеанса. Примеры предыдущего фрагмента наглядно иллюстрируют практическую реализацию описанного механизма локализации переменных в Maple-процедурах. При этом, частный случай, когда значения локальных переменных совпадают со значениями одноименных глобальных переменных, например,

> restart; P:= proc(a, b) local x, y; x, y:= 42, 64; x, y end proc:

> x, y:= 42, 64: P(a, b), x, y; ⇒ 42, 64, 42, 64

> restart; P:= proc(a, b) local x, y; x, y:= a, b; x, y end proc:

> x, y:= 42, 64: P(a, b), x, y; ⇒ a, b, 42, 64

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

По assign-процедуре в теле процедур можно назначать выражения как локальным (заданным явно), так и глобальным (заданным явно либо неявно) переменным. Однако здесь имеется одно весьма существенное отличие. Как известно, пакет не допускает динамического генерирования имен в левой части (:=)-оператора присваивания, тогда как на основе assign-процедуры это возможно делать. Это действительно существенная возможность, весьма актуальная в целом ряде задач практического программирования [103]. Между тем, если мы по процедуре assign в теле процедуры будем присваивать выражения локальным переменным и сгенерированным одноименным с ними переменным, то во втором случае присвоения производятся именно глобальным переменным, не затрагивая локальных. Нижеследующий пример весьма наглядно иллюстрирует вышесказанное.

> restart; V42, G47:= 10, 17: proc() local V42, G47; assign(V42=64, G47=59); assign(cat(V, 42)=100, cat(G, 47)=200); [V42, G47] end proc(), [V42, G47]; ⇒ [64, 59], [100, 200]

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

Следует еще раз отметить, что для предложений присвоения в процедурах в целом ряде случаев использование assign-процедуры является единственно возможным подходом. Однако, при таком подходе в общем случае требуется, чтобы левая часть уравнения x=a в assign(x=a) была неопределенным именем, т.е. для нее должно выполняться соотношение type(x, 'symbol') = true. И здесь вполне допустимо использование конструкций следующего общего формата кодирования: assign(op([unassign('<Имя>'), <Имя>]) = <Выражение>)

При этом, для таких объектов как процедуры, модули, таблицы и массивы (включая векторы и матрицы в смысле Maple, а не NAG) кодирование их имен в невычисленном формате необязательно, что может существенно облегчать программирование. Следующий весьма простой фрагмент иллюстрирует вышесказанное:

> x:= 64: assign(op([unassign(x), x]) = 59); x; ⇒ 64

Error, (in unassign) cannot unassign '64' (argument must be assignable)

> P:= proc() end proc: M:= module() end module: T:= table([]): A:= array([]):

> map(whattype, map(eval, [P, M, T, A])); ⇒ [procedure, module, table, array]

> seq(assign(op([unassign(k), k])=59),k=[P,M,T,A]); [P,M,T,A], map(type, [P,M,T,A],'odd'); [59, 59, 59, 59], [true, true, true, true]

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

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

Процедура

global (x = a)

local (x = b)

(1) global (x = a) ⇒⇒⇒⇒⇒⇒⇒⇒ ⇒⇒ global (x = a)

(2) global (x = a) ⇒⇒⇒⇒⇒⇒⇒⇒ ⇒⇒ global (x = a)

local (x = b)

(3) x ⇒⇒⇒⇒⇒⇒⇒⇒⇒ true ⇒⇒ ⇒⇒ x::{name|symbol} type(x, {'name|symbol'})

Error-ситуация

(4) (x = a) ⇒⇒⇒⇒⇒⇒ false ⇒⇒ ⇒⇒ x = a

В случае (1) явно либо неявно определенная х-переменная процедуры на всем протяжении текущего сеанса сохраняет свое значение до его переопределения вне или в самой процедуре. В случае (2) определенная локально в теле процедуры х-переменная в рамках процедуры может принимать значения, отличные от ее глобальных значений вне ее, т.е. в процедуре временно подавляется действие одноименной с ней глобальной х-переменной. Однако здесь имеют место и особые случаи (3, 4), не охватываемые стандартным механизмом. Для ряда функций, использующих ранжированные переменные (например, product, sum), возможны две ситуации, если такие переменные не декларировались в процедуре явно. Прежде всего, как отмечалось выше, не выводится предупреждающих сообщений о том, что они предполагаются локальными. Следовательно, они согласно трактовке языка Maple должны рассматриваться глобальными. Между тем, если на момент вызова процедуры, содержащей такие функции, ранжированная х-переменная была неопределенной (случай 3), то получая значения в процессе выполнения процедуры, после выхода из нее она вновь становится неопределенной, т.е. имеет место глобальное поведение переменной. Если на момент вызова процедуры х-переменная имела значение, то выполнение процедуры инициирует ошибочную ситуацию, а значение х-переменной остается неизменным (случай 4). Рассмотренные ситуации еще раз говорят в пользу явного определения входящих в процедуру переменных.

Наряду со сказанным, локальные переменные могут использоваться в теле процедур в качестве ведущих переменных с неопределенными для них значениями, например:

> y:= 64: GS:= proc(p) local y, F; F:= y -> sum(y^k, k= 0 .. n); diff(F(y), y$p) end proc: > [y, simplify(GS(2))]; ⇒  64, y(n + 1) n 2 − 2 yn n2 + y (n − 1)( ny 2 − 1 y) (3 n + 1) n + y (n − 1) n + 2 yn − 2 

Данный фрагмент иллюстрирует использование локальной y-переменной в качестве ведущей переменной F(y)-полинома от одной переменной. Вне области действия процедуры глобальная y-переменная имеет конкретное числовое значение, т.е. их области не пересекаются.

Следующий простой фрагмент иллюстрирует взаимосвязь локальных и глобальных переменных, когда вторые определяются как явно в global-секции, так и неявно через assignпроцедуру. Второй же пример фрагмента иллюстрирует механизм действия uneval-типированного формального аргумента, когда (в отличие от стандарта) ему могут присваиваться выражения на уровне глобальных переменных. Применение подобного весьма полезного приема уже иллюстрировалось в предыдущем разделе.

> P:= proc() local a; global b; x:= 56; assign('m' = 67); a:= proc(x) m:= 47; assign('n' = 89); x end proc; x*a(3) end proc:

Warning, `x` is implicitly declared local to procedure `P`

Warning, `m` is implicitly declared local to procedure `a`

> n, m:= 100, 100: P(), n, m; ⇒ 168, 89, 67

> P1:= proc(m::uneval) local a, x; global b; a, x:= 59, 39; b:= 64; assign('m'=42) end proc: > m:= 64: P1(m), a, x, b, m; ⇒ a, x, 64, 42

Еще на одном существенном моменте механизма глобальных и локальных переменных необходимо акцентировать внимание, предварительно пояснив понятие по-уровневого вычисления. Правила вычислений в Maple-языке предполагают нормальными полные вычисления для глобальных переменных и 1-уровневые для локальных. Поясним сказанное первым примером следующего простого фрагмента:

> W:= y^4; ⇒ W := y4

(1)

> y:= z^3; ⇒ y := z3

> z:= h^2; ⇒ z := h2

> h:= 3; ⇒ h := 3

> W; ⇒ 282429536481

> [eval(W, 1), eval(W, 2), eval(W, 3), eval(W, 4)]; ⇒ [y4, z12, h24, 282429536481]

(2)

> G:= proc() local W, y, z, h; W:= y^4; y:= z^3; z:= h^2; h:= 2; W end proc:

> [G(), eval(G()), evala(G()), evalf(G())]; ⇒ [y4, 16777216, y4, y4]

(3)

в котором представлена простая рекуррентная цепочка выражений, вычисление которой реализует полностью рекуррентная подстановку и обеспечивает возврат конечного числового значения, т.е. производится полное вычисление для W-выражения, идентификатор которого полагается глобальным. С другой стороны, вызов функции eval(B, n) обеспечивает n-уровневое вычисление заданного ее первым фактическим В-аргументом выражения, что иллюстрирует второй пример фрагмента.

Для полного вычисления произвольного В-выражения используется вызов eval(B)-функции. Однако в первом примере фрагмента W-переменная является глобальной, что и определяет ее полное вычисление, если (как это иллюстрирует второй пример) не определено противного. Наконец, третий пример фрагмента иллюстрирует результат вычисления той же рекуррентной цепочки выражений, но уже составляющих тело процедуры и идентификаторы которых определены в ней локальными. Из примера следует, что если не определено противного, то процедура возвращает только первый уровень вычисления W-выражения и для его полного вычисления требуется использование функции eval, как это иллюстрирует последний пример фрагмента. Данное обстоятельство следует всегда иметь в виду, ибо оно не имеет аналогов в традиционных языках программирования и наряду с требованием особого внимания обеспечивает целый ряд весьма интересных возможностей программирования в различных приложениях.

Еще на одном моменте следует остановиться отдельно. По конструкциям op(2, eval(P)) и op(6, eval(P)) возвращается соответственно последовательность локальных и глобальных переменных процедуры P. И если в первом случае это действительно так, то во втором ситуация совершенно иная, а именно. Если в процедуре производятся присвоения переменным по assign-процедуре и эти переменные явно не определены глобальными, то второй вызов их не распознает, например:

> restart; P:= proc() local a, b, c; assign( x= 64, y = 59) end proc: P(): seq([op(k, eval(P))], k = [2, 6]), x, y; ⇒ [a, b, c], [], 64, 59

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

Globals := proc(P::procedure) local a b c f k p, , , , , ; assign(a = {op 6,( eval(P))}, p = {op 2,( eval(P))}, b = [ ], c = {anames '( user')}, f = cat(CDM( ), "\$Art18_Kr10$.m"));

c := c minus {seq(`if`(cat "", ,( k " ")[1 .. 3] = "CM:", ,k NULL), k = c)};

for k in [Assign, assign, assign67, assign6, assign7 ] do try b := [op(b), op extrcalls( ,( P k))] catch "call of the form <%1> does not exist": next end try

end do;

(proc() save args, f end proc )(op(c)), Unassign(op(c)); for k in b do

parse(cat "try eval(parse(", ,( k ")) catch : NULL end try;" '), statement')

end do;

c := a, ({anames ' ( user')} minus p) minus a; read f; c, fremove( )f

end proc

> P:= proc() local a; global x, y; a:= `+`(args); 5*assign(v=64), assign(g=59), assign(s=39); a/(v + g + s) end proc: Globals(P); ⇒ {y, x}, {v, s, g}

> Globals(mwsname); ⇒ {}, {VGS_vanaduspension_14062005}

> Globals(holdof); ⇒ {_avzagnartkrarn63}, {} > Globals(`type/file`); ⇒ {_datafilestate}, {_warning}

> Proc:= proc() local a, b; global c; assign('x' = 64, 'c' = 2006), assign67('y' = 59, 39); a:=proc() local z, h; assign67('z' = 10, 17), assign(h = 59); [z], h end proc; a() end proc: [Proc()], Globals(Proc); ⇒ [[10, 17], 59], {c}, {x, y, z, h}

> restart; Proc:= proc() local a, b; global c; assign('x' = 64, 'c' = 2006), assign67('y' = 59, 39); a:=proc() local z, h; assign67('z' = 10, 17), assign(h = 59); [z], h end proc; a() end proc:

[Proc()]: c, x, [y], [z], h; ⇒ 2006, 64, [59, 39], [z], h

> restart; P1:= proc() local a; assign('a' = 64); a end proc: P1(), a; ⇒ 64, a

> restart; P1:= proc() local a; assign67('a' = 64); a end proc: P1(), a; ⇒ 64, a

С этой целью нами была определена процедура Globals, обеспечивающая более точное тестирование глобальных переменных процедур. Вызов процедуры Globals(P) возвращает 2-элементную последовательность множеств глобальных переменных процедуры, определяемой фактическим аргументом Р. Первое множество возвращаемой последовательности содержит имена глобальных переменных, определяемых global-секцией процедуры Р, тогда как второе множество содержит имена глобальных переменных, которые определяются вызовами процедур пакетной assign и наших assign6, assign7, assign67 и Assign [41,103]. Процедура Globals функционирует в среде Maple 9 и выше. При этом, следует иметь в виду следующее важное обстоятельство, а именно.

Для простой процедуры (не содержащей внутри себя других процедур) вызов Globals(P) возвращает как определенные в global-секции Р переменные, так и переменные, значения которым присваивались процедурами assign, assign6, assign7, assign67 и Assign. Совсем иначе ситуация обстоит в случае вложенных процедур, когда вложенная процедура (подпроцедура) также содержит вызовы указанных процедур, но некоторые из вычисляемых ими переменных определены в local-секции подпроцедуры. В этом случае такие переменные, идентифицируясь Globals глобальными, на самом деле носят локальный характер. Сказанное хорошо иллюстрирует пример Proc-процедуры предыдущего фрагмента (переменные z и h). Данное обстоятельство следует учитывать при использовании процедуры Globals для вложенных процедур, содержащих assign-вызовы.

В целях повышения надежности при работе с глобальными переменными может быть использована процедура uglobal, чей вызов uglobal ('x', 'y', 'z',…) обеспечивает отмену значений глобальных переменных x, y, z, … на период выполнения процедуры, в которой данный вызов был сделан. После чего процедура может произвольно их использовать.

uglobal := proc() local a b, ; assign(a = cat(CDM( ), "/$Art_Kr169$"), b = interface(warnlevel)), null interface(( warnlevel = 0));

if type(a, 'file') then read3(a); fremove(a), null interface(( warnlevel = b)) else save3(args, a); unassign(args), null(interface(warnlevel = b)) end if

end proc

> V:=64: P:=proc() global V; uglobal('V'); V:= `*`(args); V,uglobal() end proc: P(10, 17, 39),V; 6630, 64

При этом, процедура uglobal в m-файле сохраняет значения всех переменных x, y, z, …, обеспечивая возможность их последующего восстановления вызовом uglobal(). Предыдущий фрагмент представляет исходный текст uglobal-процедуры и пример конкретного ее применения при работе с глобальной V-переменной в процедуре Р.

В Maple, начиная с 9-го релиза, для встроенной функции anames был определен аргумент 'user', по которому вызов anames('user') возвращает последовательность имен, значения для которых в текущем сеансе были определены пользователем. Данная возможность важна для приложений. В целях получения некоторого аналога данного средства для Maple релизов 8 и ниже нами была определена процедура pusers, исходный текст которой и пример применения представлены нижеследующим фрагментом.

pusers := proc() local a b c d k p h, , , , , , ; assign(a = { }, b = interface(warnlevel), c = "procedure ", d = " has been activated in the current session") interface(, warnlevel = 0);

p := ({seq(`if`(cat "", ,( k " ")[1 .. 3] = "CM:", NULL k, ), k = {anames('procedure')})} minus {anames('builtin')}) minus

{Testzero pusers, }; for k in p do

unassign ' '( h ); try ParProc1(k h, ); if type(h, 'symbol') then a := {op(a), k} end if catch "invalid input: %1 expects": next end try

end do;

null interface(( warnlevel = b)), unassign '( _warning'), a end proc

> Proc:= () -> `+`(args): Proc1:= () -> `+`(args): Proc2:= () -> `+`(args): Proc3:= () -> `+`(args): Proc4:= () -> `+`(args): pusers(); ⇒ {Proc, Proc1, Proc2, Proc3, Proc4}

Вызов процедуры pusers() возвращает множество имен процедур, определенных пользователем в текущем сеансе. При этом, в него не включаются те процедуры, определения которых находятся в Maple-библиотеках пользователя, логически сцепленных с главной библиотекой пакета. Данная процедура функционирует в среде Maple релизов 6 – 10.

elib := proc(e::symbol, L::{mlib, mla}) local a k, ; for k in march '( list L', ) do

if k[1] = "" || || e ".m" then a := SUB_S [( ", " = "/"], `` || (seqstr op(( k[2][1 .. 3])))); return true, `if`(2 < nargs, assign(args[3] = a), NULL)

end if

end do;

false

end proc

> elib(pusers, "C:/program files/maple 10/lib/userlib", 'h'), h; ⇒ true, 2007/1/21

Вызов представленной выше процедуры elib(e, L) возвращает true-значение, если объект е находится в Maple-библиотеке L, и false-значение в противном случае. Если указан и третий аргумент, то через него возвращается дата сохранения е-объекта в библиотеке.

3.4. Определяющие параметры и описания Mapleпроцедур

Прежде всего, представим секцию описания (description), завершающую описательную часть определения процедуры и при ее наличии располагающуюся между секциями {local, global, uses, options} и непосредственно телом процедуры. При отсутствии данных секций description-секция располагается непосредственно за заголовком процедуры и кодируется в следующем формате: description <Строчная конструкция> {:|;}

Определенная в данной секции строчная конструкция не влияет на выполнение процедуры и используется в качестве комментирующей компоненты, т.е. она содержит текстовую информацию, предназначенную, как правило, для документирования процедуры. При этом, в отличие от обычного комментария языка, которое игнорируется при чтении процедуры, описание ассоциируется с процедурой при ее выводе даже тогда, когда ее тело не выводится по причине использования рассматриваемой ниже опции Copyright. Более того, определяемый description-секцией комментарий может быть одного из типов {string, symbol}, как это иллюстрирует следующий простой фрагмент:

> REA:= proc() description `Average`; sum(args[k], k= 1 .. nargs)/nargs end proc: > REA(19.42, 19.47, 19.62, 19, 67, 19, 89, 20.06), eval(REA);

34.07125000, proc () description Average; sum(args[k],k = 1 .. nargs)/nargs end proc > REA:= proc() option Copyright; description "Average of real arguments"; sum(args[k], k= 1 .. nargs)/nargs end proc:

> eval(REA); ⇒ proc () description "Average of real arguments" … end proc

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

Рассмотрев секции local, global и description, несколько детальнее остановимся на секции {options|option}, которая должна кодироваться непосредственно за двумя первыми (или быть первой при их отсутствии) в описательной части определения процедуры. В качестве параметров (опций) данной секции допускаются следующие: builtin, arrow, Copyright, trace, operator, remember и call_external. При этом, перечень опций может зависеть от используемого релиза пакета.

Пакет располагает тремя типами процедур: встроенными непосредственно в ядро пакета, библиотечными и определяемыми самим пользователем. Параметр builtin определяет встроенную функцию пакета и при наличии он должен кодироваться первым в списке параметров option-секции. Данный параметр визуализируется при полном вычислении процедуры посредством eval-функции либо по print-функции, например:

> print(eval), eval(readlib); proc () option builtin; 169 end proc proc () options builtin, remember; 237 end proc

Каждая встроенная функция идентифицируется уникальным номером (зависящим от номера релиза пакета) и пользователь не имеет прямой возможности определять собственные встроенные функции. В приведенном примере первым выводится результат вызова print-функции, а вторым - eval-функции, из чего следует, что встроенные функции eval и readlib имеют соответственно номера 98 и 152 (Maple 8, тогда как уже для Maple 10 эти номера соответственно будут 117 и 274), а вторая процедура имеет дополнительно и опцию remember.

Для проверки процедур могут быть полезны и три наши процедуры ParProc, ParProc1 и Sproc [103], обеспечивающие возврат как основных параметров процедур, модулей и пакетов, так и их местоположение в библиотеках Maple, как это иллюстрирует следующий достаточно простой фрагмент:

> ParProc(MkDir), ParProc(came); map(ParProc, ['add', march, goto, iostatus, seq]);

locals = Arguments(cd r k h z K L, , , = , ,( ::F ,{string symbol, Λ, ,t d, , ω, u f s g v, , ,}) , ), Argumentsglobalslocals = = ( = __Art_Kr_(E(::f h,anything) ) )

[builtin function, 91, iolib function, 31, builtin function, 193, iolib function, 13, builtin function, 101]

> ParProc(DIRAX);

DIRAX is module with exports [new,replace,extract,empty,size,reverse,insert,delete,sortd,printd,conv]

> ParProc(process); ⇒ inert_function process is module with exports [popen, pclose, pipe, fork, exec, wait, block, kill, launch]

> ParProc(Int);

Warning, <Int> is inert version of procedure/function <int>

> ParProc1(ParProc1, 'h'), h;

Warning, procedure ParProc1 is in library [Proc, User, {"c:/program files/maple

10/lib/userlib"}]

localsArguments = globals(a b c d p h t z cs L R N, , , , , , , , = = ((M_62 ParProc Sproc::{,procedure,, , module, , ), ω ν},) ),

[Proc User, , {"c:/program files/maple 10/lib/userlib"}] > ParProc1(Sockets, 't'), t;

Warning, module Sockets is in library [package, Maple, {"C:\Program Files\Maple 10/lib"}]

[exports = (socketID, Open, Close, Peek, Read, Write, ReadLine, ReadBinary,

WriteBinary Server Accept Serve Address ParseURL LookupService, , , , , , ,

GetHostName GetLocalHost GetLocalPort GetPeerHost GetPeerPort, , , , ,

GetProcessID HostInfo Status Configure _pexports, , , , )]

[locals = (defun trampoline soPath solib passign setup finalise, , , , , , )]

[options = (package, noimplicit, unload = finalise, load = setup)]

[description = ("package for connection oriented TCP/IP sockets" ,)]