Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программная реализация библиотеки векторной, матричной и кватернионной алгебр.docx
Скачиваний:
2
Добавлен:
01.07.2025
Размер:
190 Кб
Скачать
☆
      1. Реализация на основе записей (record)

Тип record (запись) в современных версиях Delphi может использоваться не только для традиционного представления структур данных в виде простой совокупности полей, но также позволяет объявлять более сложные типы, во многом подобные классам. В дополнение к полям, записи могут содержать свойства, методы (включая конструкторы) и объявления вложенных типов. Также в записях допускаются так называемые классовые поля, свойства и методы (объявляемые с ключевым словом class).

Но записи имеют также и ряд важных отличий от классов:

  • Записи не поддерживают наследование.

  • Если объекты записи не объявлены глобальными или для их создания и удаления не используются явные вызовы функций New и Dispose, то память для таких объектов выделяется на стеке, они копируются при присваивании и передаются в функции и обратно по значению. Классы же относятся к ссылочным типам, их объекты не копируются при присваивании, передаются по ссылке, а память для них выделяется в «куче» после явного вызова конструктора.

  • Записи позволяют перегружать операторы, а классы – нет.

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

  • Записи не могут иметь деструкторов.

  • Записи не могут иметь виртуальных методов (объявляемых со спецификаторами virtual, dynamic или message).

  • Записи не могут реализовывать интерфейсы.

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

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

Ниже представлен пример объявления структур типа вектор (TVector), матрица (TMatrix) и кватернион (TQuaternion) в рамках описанного подхода (содержимое секции Interface модуля с комментариями). Диаграмма классов не приводится в связи с её тривиальностью.

Листинг 1.

Interface

// Объявления новых типов

Type

// Опережающая декларация ссылочного типа на структуру типа TQuaternion

PQuaternion = ^TQuaternion;

// Структура типа вектор

TVector = record

private

// Массив элементов

data: array of Extended;

// Функция получения значения элемента

function GetItem(i: Integer): Extended;

// Процедура установки значения элемента

procedure SetItem(i: Integer; value: Extended);

// Функция получения размера вектора

function GetSize: Integer;

// Функция получения индекса последнего элемента

function GetHigh: Integer;

public

// Функция создания копии объекта

function Clone: TVector;

// Функция изменения размера вектора

procedure Resize(n: Integer);

// Оператор неявного преобразования обычного массива в TVector

class operator Implicit(a: array of Extended): TVector;

// Оператор унарный минус

class operator Negative(a: TVector) : TVector;

// Оператор сложения векторов

class operator Add( a, b: TVector ): TVector;

// Оператор вычитания векторов

class operator Subtract( a, b: TVector ): TVector;

// Оператор умножения вектора на число

class operator Multiply( a: TVector; b: Extended ): TVector; overload;

// Оператор умножения числа на вектор

class operator Multiply( a: Extended; b: TVector): TVector; overload;

// Оператор скалярного произведения векторов

class operator Multiply( a, b: TVector ): Extended; overload;

// Функция векторного произведения векторов

function Cross( a: TVector ): TVector;

// Функция получения модуля вектора

function Length: Extended;

// Поворот векора вокруг заданной оси на заданный угол при помощи формулы Родрига

function rotateByRodrigFormula(phi: Extended; axis: TVector): TVector;

// Поворот векора вокруг заданной оси на заданный угол при помощи кватерниона

function rotate(phi: Extended; axis: TVector): TVector;

// Поворот векора вокруг заданной оси на заданный угол при помощи кватерниона

function rotateByQuaternion(Q: PQuaternion): TVector;

// Процедура нормирования вектора

procedure Norm;

// Свойство для чтения и изменения размера вектора

property Size: Integer read GetSize write Resize;

// Свойство для чтения индекса последнего элемента

property High: Integer read GetHigh;

// Свойство для доступа к элементам вектора

property Item[i: Integer]: Extended read GetItem write SetItem; default;

end;

// Структура типа матрица

TMatrix = record

private

// Двумерный массив элементов

data: array of array of Extended;

// Функция получения значения элемента

function GetItem(i,j: Integer): Extended;

// Процедура установки значение элемента

procedure SetItem(i,j: Integer; value: Extended);

// Функция получения кол-ва строк

function GetRowCount: Integer;

// Функция получения кол-ва столбцов

function GetColCount: Integer;

// Функция получения индекса последней строки

function GetRowHigh: Integer;

// Функция получения индекса последнего столбца

function GetColHigh: Integer;

public

// Функция создания копии объекта

function Clone: TMatrix;

// Функция изменения размерности

procedure Resize(n, m: Integer);

// Оператор унарный минус

class operator Negative(a: TMatrix): TMatrix;

// Оператор сложения матриц

class operator Add(a, b: TMatrix): TMatrix;

// Оператор вычитания матриц

class operator Subtract(a, b: TMatrix): TMatrix;

// Оператор умножения матрицы на матрицу

class operator Multiply(a, b: TMatrix): TMatrix; overload;

// Оператор умножения матрицы на число

class operator Multiply(a: TMatrix; b: Extended): TMatrix; overload;

// Оператор умножения числа на матрицу

class operator Multiply(a: Extended; b: TMatrix): TMatrix; overload;

// Оператор умножения матрицы на вектор

class operator Multiply(a: TMatrix; b: TVector): TVector; overload;

// Оператор умножения вектора на матрицу

class operator Multiply(a: TVector; b: TMatrix): TVector; overload;

// Функция вычисления определителя

function Det: Extended;

// Функция обращения матрицы

function Inv: TMatrix;

// Функция транспонирования матрицы

function T: TMatrix;

// Порождающая функция для формирования единичной матрицы

class function E(n: Integer): TMatrix; static;

// Процедура перестановки строк

procedure SwapRows(i, j: Integer);

// Свойство для достуа к элементам матрицы

property Item[i,j: Integer]: Extended read GetItem write SetItem; default;

// Свойство для получения строки матрицы, как вектора

property Row[i: Integer]: TVector read GetRow;

// Свойство для чтения количества строк

property RowCount: Integer read GetRowCount;

// Свойство для чтения количества столбцов

property ColCount: Integer read GetColCount;

// Свойство для чтения индекса последней строки

property RowHigh: Integer read GetRowHigh;

// Свойство для чтения индекса последнего столбца

property ColHigh: Integer read GetColHigh;

end;

// Структура типа кватернион

TQuaternion = record

private

// Скалярная часть кватерниона

q0: extended;

// Скалярная часть кватерниона

Q: TVector;

// Функция получения значения элемента

function GetItem(i: Integer): Extended;

// Функция получения векторной части кватерниона

function GetVect: TVector;

public

// Функция создания копии объекта

function Clone: TQuaternion;

// Производящая функция для создания кватерниона по его компонентам

class function fromElements(q0, q1, q2, q3: Extended): TQuaternion; static;

// Производящая функция для создания кватерниона по углу и оси поворота

class function fromAngleAndAxis(phi: Extended; e: TVector): TQuaternion; static;

// Производящая функция для создания кватерниона по углам Крылова

class function fromKrylovAngles(yaw, pitch, roll: Extended): TQuaternion; static;

// Оператор унарный минус

class operator Negative( a: TQuaternion ): TQuaternion;

// Оператор сложения кватернионов

class operator Add( a, b: TQuaternion ): TQuaternion;

// Оператор вычитания кватернионов

class operator Subtract( a, b: TQuaternion ): TQuaternion;

// Оператор умножения кватернионов

class operator Multiply( a, b: TQuaternion ): TQuaternion; overload;

// Оператор умножения кватерниона на число

class operator Multiply( a: TQuaternion; b: Extended ): TQuaternion; overload;

// Оператор умножения числа на кватернион

class operator Multiply( a: Extended; b: TQuaternion ): TQuaternion; overload;

// Оператор умножения кватерниона на вектор

class operator Multiply( a: TQuaternion; b: TVector ): TQuaternion; overload;

// Оператор умножения вектора на кватернион

class operator Multiply( a: TVector; b: TQuaternion ): TQuaternion; overload;

// Функция нормирования кватерниона

function norm: TQuaternion;

// Функция получения сопряженного кватерниона

function conj: TQuaternion;

// Функция обращения кватерниона

function inv: TQuaternion;

// Функция получения матрицы вращения из компонентов кватерниона

function toRotateMatrix: TMatrix;

// Свойство для чтения к элементам кватерниона

property Item[i: Integer]: Extended read GetItem; default;

// Свойство для чтения векторной части кватерниона

property Vect: TVector read GetVect;

// Свойство для чтения скалярной части кватерниона

property Scal: Extended read q0;

end;

Важно отметить метод Clone, который обеспечивает создание в памяти копии объекта, для которого он вызывается. Использование этого метода обязательно при необходимости выполнить прямое присваивание одного объекта другому: А:=B.Clone. Недопустимо использовать конструкцию вида A:=B из-за того, что при таком присваивании область памяти, в которой хранятся данные объекта B, не будет скопирована, а в объект A будет перенесён лишь указатель на неё, хранящийся в поле data.

Ключевое слово default, указанное после деклараций индексированных свойств доступа к элементам вектора, матрицы и кватерниона, позволяет обращаться к этим свойствам неявно – без указания их имени, например: A[i] вместо A.Item[i], где А – экземпляр структуры TVector.

Использование ссылочного типа PQuaternion для аргумента функции rotateByQuaternion структуры TVector вызвано невозможностью применить опережающую декларацию непосредственно к записям (record) в отличие от классов. Между тем, какая-то альтернатива такой декларации необходима, так как структура TQuaternion объявляется позже структуры TVector, аргумент упомянутого метода которой является кватернионом. Выходом и является использование ссылочного типа в опережающей декларации. При обращении к переменной ссылочного типа как к экземпляру соответствующей структуры следует использовать оператор «^» после имени переменной, например: Q^.

Применение перегруженных операторов позволяет впоследствии записывать расчётные соотношения в коде наиболее естественным образом. например: C := A+B.

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

Листинг 2.

Implementation

uses SysUtils;

// Функция получения значения элемента

function TVector.GetItem(i: Integer): Extended;

begin

Result := data[i];

end;

// Процедура установки значения элемента

procedure TVector.SetItem(i: Integer; value: Extended);

begin

data[i] := value;

end;

// Функция создания копии объекта

function TVector.Clone: TVector;

begin

Result := self;

Result.Resize(self.Size);

end;

// Функция изменения размера вектора

procedure TVector.Resize(n: Integer);

begin

SetLength(data, n);

end;

// Функция получения размера вектора

function TVector.GetSize: Integer;

begin

Result := System.Length(data);

end;

// Функция получения индекса последнего элемента

function TVector.GetHigh: Integer;

begin

Result := System.High(data);

end;

// Оператор неявного преобразования к обычному динамическому массиву

class operator TVector.Implicit(a: array of Extended): TVector;

var

n, i: Integer;

begin

n := System.Length(a);

Result.Resize(n);

for i := n-1 downto 0 do

Result.data[i] := a[i];

end;

// Оператор сложения векторов

class operator TVector.Add( a, b: TVector ): TVector;

var

i: Integer;

begin

{$IFDEF Debug}

if a.Size <> b.Size then

raise Exception.Create('Несовместимые размерности');

{$ENDIF}

Result.Resize(a.Size);

for i := 0 to a.High do

Result[i] := a[i] + b[i];

end;

// Поворот вектора вокруг заданной оси на заданный угол при помощи кватерниона

function TVector.rotateByQuaternion(Q: PQuaternion): TVector;

begin

RESULT := (Q^ * self * Q.conj).Vect;

end;

// Функция получения значения элемента матрицы

function TMatrix.GetItem(i, j: Integer): Extended;

begin

Result := data[i, j];

end;

// Функция установки значение элемента

procedure TMatrix.SetItem(i, j: Integer; value: Extended);

begin

data[i, j] := value;

end;

// Функция для получения строки матрицы как вектора

function TMatrix.GetRow(i: Integer): TVector;

var

n, j: Integer;

begin

n := GetColCount;

Result.Resize(n);

for j := n-1 downto 0 do

Result.data[j] := data[i, j];

end;

// Функция получения количества строк

function TMatrix.GetRowCount;

begin

Result := Length(data);

end;

// Функция получения количества столбцов

function TMatrix.GetColCount;

begin

if Length(data) > 0 then

Result := Length(data[0])

else

Result := 0;

end;

// Функция получения индекса последней строки

function TMatrix.GetRowHigh: Integer;

begin

Result := GetRowCount - 1;

end;

// Функция получения индекса последнего столбца

function TMatrix.GetColHigh: Integer;

begin

Result := GetColCount - 1;

end;

// Функция создания копии объекта

function TMatrix.Clone: TMatrix;

begin

Result := self;

Result.Resize(self.RowCount, self.ColCount);

end;

// Функция изменения размерности

procedure TMatrix.Resize(n, m: Integer);

begin

SetLength(data, n, m);

end;

// Оператор умножения матрицы на матрицу

class operator TMatrix.Multiply( a, b: TMatrix ): TMatrix;

var

i, j, k: Integer;

begin

{$IFDEF Debug}

if (a.ColCount <> b.RowCount) then

raise Exception.Create('Несовместимые размерности');

{$ENDIF}

Result.Resize(a.RowCount, b.ColCount);

for i := 0 to Result.RowHigh do

for j := 0 to Result.ColHigh do

begin

Result[i,j] := 0;

for k := 0 to a.ColHigh do

Result[i,j] := Result[i,j] + a[i,k] * b[k,j];

end;

end;

// Функция получения значения элемента кватерниона

function TQuaternion.GetItem(i: Integer): Extended;

begin

{$IFDEF Debug}

if (i < 0) or (i > 3) then

raise Exception.Create('Неверный индекс элемента кватерниона');

{$ENDIF}

if i = 0 then

Result := q0

else

Result := Q[i-1];

end;

// Функция получения векторной части кватерниона

function TQuaternion.GetVect: TVector;

begin

Result := Q.Clone;

end;

// Функция создания копии объекта

function TQuaternion.Clone: TQuaternion;

begin

Result.q0 := q0;

// При копировании векторной части используется метод Clone

Result.Q := Q.Clone;

end;

// Производящая функция для создания кватерниона по его компонентам

class function TQuaternion.fromElements(q0, q1, q2, q3: Extended): TQuaternion;

begin

Result.q0 := q0;

Result.Q.Resize(3);

Result.Q[0] := q1;

Result.Q[1] := q2;

Result.Q[2] := q3;

end;

// Производящая функция для создания кватерниона по углу и оси поворота

class function TQuaternion.fromAngleAndAxis(phi: Extended; e: TVector): TQuaternion;

begin

{$IFDEF Debug}

if (e.Size <> 3) then

raise Exception.Create('Неверно задан вектор оси поворота');

{$ENDIF}

Result.q0 := cos(phi/2);

Result.Q := e.Clone;

Result.Q.norm();

Result.Q := Result.Q * sin(phi/2);

end;

// Оператор умножения кватернионов

class operator TQuaternion.Multiply( a, b: TQuaternion ): TQuaternion;

begin

Result.q0 := a.q0 * b.q0 – a.Q * b.Q;

Result.Q := a.Q * b.q0 + b.Q*a.q0 + a.Q.Cross(b.Q);

end;

// Оператор умножения кватерниона на вектор

class operator TQuaternion.Multiply( a: TQuaternion; b: TVector ): TQuaternion;

begin

Result := a * TQuaternion.fromElements(0, b[0], b[1], b[2]);

end;

Следует обратить внимание на использование директивы условной компиляции {$IFDEF Debug}…{$ENDIF} в реализации методов сложения векторов, умножения матриц и др. Она позволяет, в зависимости от режима компиляции, включить или исключить из программы некоторый код, который помещён между операторами директивы {$IFDEF Debug} и {$ENDIF}. Параметр Debug является системным параметром среды разработки Delphi RAD Studio, который принимает значение TRUE в случае компиляции программы в режиме отладки, и FALSE – в противном случае. В нашем случае такой код может осуществлять контроль размерностей операндов (например, при умножении матриц) и генерирует специальный объект исключения класса Exception с сообщением об ошибке (для этого используется оператор raise). В режиме отладки такой контроль может быть полезен для недопущения обращения к «чужой» области памяти и облегчения поиска ошибок в программе. При возникновении исключительной ситуации работа программы будет прервана, а на экране в диалоговом окне будет отображено соответствующее сообщение. После окончания отладки и компиляции программы в рабочем режиме такой код в подавляющем большинстве случаев становится избыточным – в корректно работающей программе моделирования функционирования интегрированных систем ЛА не должно возникать ситуаций несовпадения размерностей операндов в векторной и матричной алгебре. Использование описанной конструкции в таком случае обеспечивает исключение этого фрагмента кода из программы при компиляции в рабочем режиме, что приведёт к увеличению её быстродействия.

Пример использования объектов, имеющих описанные структуры:

Листинг 3.

var

// Объявление переменных – экземпляров структур типа матрица, вектор, кватернион

A, B, C: TMatrix;

X, Y, Z: TVector;

L: TQuaternion;

begin

// Установка размерности матриц А, В и вектора X

A.Resize(3, 3);

B.Resize(3, 3);

X.Resize(3);

// Инициализация элементов матриц и вектора

A[0, 0] := 1; A[0, 1] := 2; …

B[0, 0] := 10; B[0, 1] := 20; …

X[0] := 1; X[1] := 2; X[2] := 3;

// Инициализация вектора массивом чисел типа Extended

Y := [5.1, 6.3, 7.2];

// Сложение матриц А и В и присваивание результата матрице С

C := A + B;

// Умножение матрицы С на вектор X и присваивание результата вектору Z

Z : = C * X;

// Инициализация кватерниона Q с помощью производящей функции по значению угла поворота и вектора оси вращения

Q := TQuaternion.fromAngleAndAxis(Pi/2, X);

// Умножение числа на вектор

Z := 2 * X;

// Поворот вектора Z при помощи кватерниона Q и присваивание результата вектору X

X := Z.rotateByQuaternion(Q);

// Создание копии вектора X и присваивание её вектору Y

Y := X.Clone;

end;

В данном фрагменте кода продемонстрировано объявление переменных типа вектор, матрица и кватернион, а также использование этих переменных в некоторых расчётах.