Навигация:
<< >> Оглавление Указатель

Описание пользователя

Глава 3. Использование стандартных модулей автокомпиляции

§3.6. Принципы создания автокомпилируемых моделей блоков

§3.6.2. Работа со статическими переменными блока

§3.6.2.2. Модели с матрицами

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

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

Как и для простых статических переменных, для матриц модуль автокомпиляции создает специальные классы доступа и добавляет в класс блока rdsbcppBlockClass по одному объекту для каждой матрицы, причем имена этих объектов совпадают с именами переменных блока. В результате внутри фрагментов программ, вводимых пользователем, к матрицам, являющимся статическими переменными блока, можно обращаться просто по именам. Для доступа к конкретному элементу матрицы используется стандартный синтаксис языка C с квадратными скобками: если, например, матрица имеет имя M, ее элемент в строке r и столбце c записывается как «M[r][c]». В классы матриц, создаваемые модулем автокомпиляции, включаются различные функции-члены для определения числа строк и столбцов в матрице, установки ее размера и т.п., все эти функции можно использовать в реакциях на события. Ниже приведены основные функции-члены классов матриц (во всех примерах предполагается, что M и M1 – матрицы переменных типа double):

вспомогательный_тип operator[](int row)
Обращение к строке матрицы. Здесь row – целый номер строки, начинающийся с нуля, а вспомогательный_тип – специальный тип, такой, что применение к нему еще одного оператора «[col]» вернет элемент матрицы в строке row и столбце col. Например, для матриц вещественных чисел таким типом будет «double*», т.е. «указатель на double». Таким образом, «M[row][col]» позволяет обратиться к элементу матрицы M, находящемуся в строке row и столбце col. Такая запись может находиться как в левой, так и в правой части выражения, то есть можно не только получать значения элементов матриц, но и присваивать их. По умолчанию проверка индексов не производится, и попытка обратиться к элементу матрицы за пределами ее текущего размера вызовет критическую ошибку. Проверку индексов можно включить, установив в параметрах модели флажок «проверять индексы в массивах и матрицах (медленно)»), при этом попытка обращения к элементу за пределами матрицы оператором «[ ]» вызовет остановку расчета и сообщение об ошибке. Следует учитывать, что включение этой проверки замедляет работу модели, поэтому проверять индексы в уже отлаженной модели следует вручную перед обращением к элементам матрицы. Примеры использования оператора:
  M[r][c]=2.0;
  double x=M[10][0];
  double y=sin(M[r][c]);
тип_элемента & Item(int row,int col)
Обращение к элементу матрицы при помощи одной функции. Здесь row – номер строки, col – номер столбца (оба номера начинаются с нуля), тип_элемента – тип элемента матрицы (для матриц вещественных чисел, например, это будет тип double). Эта функция всегда выполняет проверку допустимости индексов независимо от установок параметров модели, поэтому она работает медленнее оператора «[ ]». Как и указанный оператор, ее вызов может находиться и в левой, и в правой части выражения. Примеры использования функции:
  M.Item(r,c)=2.0;
  double x=M.Item(10,0);
  double y=sin(M.Item(r,c));
IsEmpty(void)
Возвращает TRUE, если матрица пустая (0×0), и FALSE, если в ней есть элементы. Пример использования функции:
  if(M.IsEmpty())
    return; // В матрице нет элементов
HasData(void)
Возвращает TRUE, если в матрице есть элементы, и FALSE, если она пустая. Пример использования функции:
  if(M.HasData()) // Обработка данных матрицы
    { … }
int Cols(void)
Число столбцов матрицы. Для пустой матрицы возвращается 0. Пример использования функции:
  int ncolumns=M.Cols();
int Rows(void)
Число строк матрицы. Для пустой матрицы возвращается 0. Пример использования функции:
  int nrows=M.Rows();
Resize(int rows,int cols, keep=FALSE)
Изменяет размер матрицы. Здесь rows – новое число строк матрицы, cols – новое число столбцов, необязательный параметр keepTRUE, если при изменении размера нужно сохранить текущее содержимое матрицы, и FALSE, если ее всю нужно заполнить значением элемента по умолчанию. Если параметр keep не указан, после изменения размера вся матрица будет заполнена значением по умолчанию. Функция возвращает TRUE, если изменение размера выполнено успешно. В большинстве случаев результат возврата функции можно не проверять – по крайней мере, пока идет работа с матрицами обозримых размеров. Если передать в параметрах rows и cols нулевые значения, матрица станет пустой. Примеры использования функции:
  // Установить размер 3x4
  M.Resize(3,4);
  // Добавить к матрице строку с сохранением содержимого
  M.Resize(M.Rows()+1,M.Cols(),TRUE);
  // Сделать матрицу пустой
  M.Resize(0,0);
класс_матрицы & operator=(const класс_матрицы &matr)
Оператор присваивания, позволяющий копировать одну матрицу в другую (матрицы должны быть одинаковых типов). Здесь класс_матрицы – имя класса, созданного модулем автокомпиляции для матриц данного типа, matr – копируемая матрица. Необходимость полностью скопировать одну матрицу в другую возникает достаточно редко, тем не менее, этот оператор позволяет выполнить ее без использования цикла по элементам. Следует учитывать, что обе матрицы должны обязательно быть одного и того же типа – нельзя, например, скопировать таким образом матрицу целых чисел в матрицу вещественных. Копирование матриц разного типа необходимо производить вручную поэлементно. Пример использования оператора:
  M1=M; // Скопировать матрицу M в матрицу M1

В качестве примера создадим модель блока, который будет складывать матрицы вещественных чисел, поступающие на его входы «X1» и «X2» и выдавать результат на выход «Y». Причем, если размеры «X1» и «X2» отличаются, будем считать недостающие в них строки и столбцы заполненными нулями – таким образом, размер выходной матрицы «Y» будет максимальным из размеров «X1» и «X2» и по числу строк, и по числу столбцов. Например, складывая матрицы 2×3 и 3×2, мы получим на выходе матрицу размером 3×3. Наш блок будет иметь следующую структуру переменных:

Имя Тип Вход/выход Пуск Начальное значение
Start Сигнал Вход 1
Ready Сигнал Выход 0
X1 Матрица double Вход [ ] 0
X2 Матрица double Вход [ ] 0
Y Матрица double Выход [ ] 0

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

  // Локальные переменные
  int maxrows,maxcols;
  double v1,v2;

  // Определение максимального размера матриц X1 и X2
  maxrows=max(X1.(),X2.());
  maxcols=max(X1.(),X2.());

  // Задание размера выходной матрицы
  Y.(maxrows,maxcols);

  // Цикл по элементам
  for(int r=0;r<maxrows;r++) // r – строка
    for(int c=0;c<maxcols;c++) // c – столбец
      { // v1 – элемент из X1
        if(r<X1.() && c<X1.()) // [r,c] – в X1
          v1=X1[r][c];
        else // [r,c] – за пределами X1
          v1=0.0;
        // v2 – элемент из X2
        if(r<X2.() && c<X2.()) // [r,c] – в X2
          v2=X2[r][c];
        else // [r,c] – за пределами X2
          v2=0.0;
        // Запись суммы в элемент Y
        Y[r][c]=v1+v2;
      }

Этот фрагмент программы будет выполняться в каждом такте расчета, перед которым сработали связи, подключенные к «X1» или «X2» (это обеспечат флажки в колонке «пуск» напротив этих переменных). Сначала мы записываем во вспомогательную переменную maxrows наибольшее из чисел строк X1 и X2, а в maxcols – наибольшее из чисел столбцов. Далее, вызывая функцию-член Resize у выходной матрицы Y, мы делаем размер этой матрицы равным maxrows×maxcols. Матрица Y готова, теперь нужно записать в ее элементы суммы соответствующих элементов матриц X1 и X2.

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

  // Цикл по элементам
  for(int r=0;r<maxrows;r++) // r – строка
    for(int c=0;c<maxcols;c++) // c – столбец
      Y[r][c]=X1[r][c]+X2[r][c];

Это было бы верно, если бы мы ограничились только суммированием матриц одинаковых размеров. Однако, мы решили при несовпадении размеров матриц дополнять их нулями, поэтому, прежде, чем обращаться к элементу «X1[r][c]» или «X2[r][c]», нужно проверить, есть ли элемент (r,c) в каждой из этих матриц. Действительно, переменная r изменяется от нуля до maxrows−1, а maxrows – это максимальный из двух вертикальных размеров матриц. Если в X1 будет две строки, а в X2 – три, то maxrows будет равно трем, и, когда r примет значение maxrows−1, то есть два, выполнение оператора «X1[r][c]», вероятнее всего, вызовет критическую ошибку, поскольку в X1 всего две строки с индексами 0 и 1, и элемент X1[2][c] в ней отсутствует. Поэтому в цикле суммируются не непосредственно элементы матриц X1 и X2, а вспомогательные переменные v1 и v2, равные элементам соответствующих матриц, если в этих матрицах есть элемент (r,c), и нулю в противном случае.

Можно заметить, что в этой модели нет проверки элементов входных матриц на значение rdsbcppHugeDouble. Можно не добавлять ее: единственная выполняемая в модели математическая операция – это сложение, а сложение значения rdsbcppHugeDouble с числом даст в результате то же самое значение rdsbcppHugeDouble (то есть HUGE_VAL).

Для тестирования созданной модели следует собрать схему, изображенную на рис. 371 (перед присоединением связей к созданному блоку необходимо скомпилировать модель, чтобы заданная в ней структура переменных была записана в этот блок). В этой схеме к входам блока присоединены стандартные библиотечные блоки ввода матриц вещественных чисел, а к выходу – стандартный блок отображения таких матриц. Эти блоки вводят и отображают матрицы в отдельных окнах, которые изображены на рисунке рядом с каждым блоком. Если теперь задать обе входные матрицы и запустить расчет, в окне выходной матрицы можно будет увидеть их сумму. На рисунке матрица X1 имеет размер 2×3, а X2 – 3×2, поэтому наш блок сложил эти матрицы, дополнив X1 нулевой строкой снизу, а X2 – нулевым столбцом справа, и результат получил размер 3×3.

Тестирование модели сложения матриц

Рис. 371. Тестирование модели сложения матриц

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

  // Обращение к элементу [1,2] матрицы, находящейся в Z[3,4]
  double x=Z[3][4][1][2];
  // Задание размера матрицы, находящейся в Z[5,6]
  Z[5][6].(3,3);
  // Задание размера Z и ее элемента и заполнение элементов этого элемента
  Z.(10,10);     // Задание размера Z
  Z[0][0].(5,5); // Задание размера элемента Z[0,0]
  for(int i=0;i<5;i++) // Занесение значений
    for(int j=0;j<5;j++)
       Z[0][0][i][j]=i+j;

Если элементами матрицы являются структуры, у этих элементов можно обращаться к полям, как и у обычных переменных-структур. Точно так же, у строк, являющихся элементами матриц, можно вызывать те же функции-члены, что и у обычных переменных-строк. Работа со структурами и строками рассмотрена в §3.6.2.4 и §3.6.2.5 соответственно.


<< >> Оглавление Указатель