Текущий раздел:

Описания RDS

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

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

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

§3.6.4. Моделирование длящихся во времени процессов

§3.6.4.2. Система дифференциальных уравнений и задание начальных условий

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

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

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

#### §3.6.4. Моделирование длящихся во времени процессов

##### §3.6.4.2. Система дифференциальных уравнений и задание начальных условий

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

В §3.6.4.1 рассмотрены примеры процессов, описываемых одним дифференциальным уравнением первого порядка. Если процесс описывается уравнением более высокого порядка или системой уравнений, принципы моделирования не меняются: порядок уравнения понижается заменой его на систему уравнений первого порядка, система записывается в нормальной форме Коши, после чего каждое из получившихся уравнений преобразуется в разностное одним из методов численного интегрирования – например, методом Эйлера.

Рис. 404. Тело в свободном полете

Рассмотрим движение объекта в двух измерениях под действием силы тяжести. Будем рассчитывать полет тела, брошенного с заданной начальной скоростью v0 под заданным углом α к горизонту (рис. 404). Направим ось x горизонтально в направлении полета, y – вертикально вверх. Чтобы упростить пример и не связываться с вычислением влияния сопротивления воздуха на полет тела, будем считать, что все это происходит в безвоздушном пространстве – например, на Луне (в §2.14.2 руководства программиста решается такая же задача, но на Земле с учетом сопротивления воздуха). На тело, находящееся в свободном полете, действует только сила тяжести, которая сообщает ему ускорение, равное ускорению свободного падения и направленное вертикально вниз. В горизонтальном направлении никаких сил на тело не действует, и, поэтому, никаких ускорений в горизонтальном направлении телу не сообщается. Горизонтальное и вертикальное движение тела можно рассматривать независимо, раскладывая скорость v на горизонтальную и вертикальную проекции vx и vy соответственно, а ускорение a – на ax и ay. При этом горизонтальная проекция ускорения ax на всем протяжении полета равна нулю (нет горизонтальных сил), а вертикальная ay – ускорению свободного падения g со знаком минус (ось y направлена вверх, ускорение свободного падения – вниз):

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

Эта система состоит из двух уравнений второго порядка. Чтобы понизить порядок, вспомним, что скорость – это производная координаты по времени, а ускорение – производная скорости по времени. Введя в уравнение скорости, мы получим вместо двух уравнений второго порядка четыре уравнения первого:

Теперь необходимо определить начальные условия для этой системы. Будем считать, что объект начинает движение из начала координат, поэтому x(0)=y(0)=0. Начальную скорость объекта разложим на проекции по осям координат v0x и v0y – эти значения и будут начальными условиями для скоростей:

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

Эта система легко решается аналитически, но мы рассматриваем моделирование процессов с дискретным временем, поэтому будем решать ее численно. Система записана в нормальной форме Коши, и из нее легко получить разностные уравнения методом Эйлера:

Буквой h здесь обозначен шаг расчета, то есть интервал между соседними моментами дискретного времени. Для удобства записи номер отсчета мы снова перенесли в верхний индекс: yk – это не «y в степени k», это «k-й отсчет переменной y».

Из первого уравнения этой системы видно, что горизонтальная составляющая скорости vx в процессе полета не изменяется и всегда равна v0 cos α, следовательно, вычислять ее в модели блока на каждом шаге не нужно.

Создадим блок, который будет вычислять траекторию полета тела по полученным разностным уравнениям. Выходами нашего блока будут вещественные переменные «x» и «y», в которых мы будем записывать вычисленные координаты тела. В вещественных переменных «vx» и «vy» мы будем хранить и вычислять проекции скорости тела – эти переменные можно сделать внутренними. Для упрощения примера вместо модуля начальной скорости v0 и ее угла к горизонту α мы будем просто вводить вычисленные вручную значения проекций начальной скорости в значения по умолчанию переменных «vx» и «vy» (позже мы изменим модель так, чтобы можно было непосредственно подавать на вход блока угол и начальную скорость). Будем считать, что мы бросаем наше тело со скоростью 3 м/с под углом 50° к горизонту – при этом начальное значение «vx» должно равняться 3⋅cos 50° = 1.928, а начальное значение «vy» – 3⋅sin 50° = 2.298. Кроме указанных переменных нам еще потребуется внутренняя переменная для хранения значения времени предыдущего шага расчета – как и в рассмотренных ранее примерах, назовем ее «t0».

Структура переменных нашего блока будет такой:

Имя | Тип | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | ---
Start | Сигнал | Вход | ✓ | 0
Ready | Сигнал | Выход |  | 0
x | double | Выход |  | 0
y | double | Выход |  | 0
vx | double | Внутренняя |  | 1.928
vy | double | Внутренняя |  | 2.298
t0 | double | Внутренняя |  | 0

Создадим блок с автокомпилируемой моделью, зададим для него запуск по сигналу, введем в редакторе модели указанную выше структуру переменных и Присоединим блок к динамической переменной «DynTime». Параметры модели по умолчанию изменять не будем – она будет автоматически запускаться при изменении «DynTime» и блокировать реакции при отсутствии этой переменной в схеме. На вкладке «модель» редактора необходимо ввести программу, соответствующую полученным выше разностным уравнением (за исключением уравнения для «vx» – эта переменная не изменяется, и уравнение для нее не нужно). В отличие от примеров из §3.6.4.1, в этой модели будет три уравнения, а не одно, и, из-за этого, в написании программы для нее возникает особенность, на которую следует обратить внимание.

Кажется логичным просто перенести уравнения для x, vy и y в модель в виде операторов присваивания следующим образом (будем считать, что в переменной g записано значение ускорения свободного падения):

      x=x+(DynTime-t0)*vx;	// x[k+1]  = x[k] + h vx[k]
      vy=vy-(DynTime-t0)*g;	// vy[k+1] = vy[k] – h g
      y=y+(DynTime-t0)*vy;	// y[k+1]  = y[k] + h vy[k]

Однако, если внимательно посмотреть на вторую и третью строчки этой программы, можно увидеть, что во второй строчке в переменную vy записывается вычисленное значение vyk+1, а в третьей это же самое значение в правой части оператора присваивания умножается на шаг расчета (DynTime-t0) для вычисления yk+1. Но ведь в правой части третьего уравнения должно находиться vyk, а не vyk+1. Во второй строчке мы потеряли значение vy на предыдущем шаге расчета, записав в эту же переменную новое значение, и теперь нам неоткуда взять его для правой части следующего уравнения. Получается, что третье уравнение будет «обгонять» остальные на один шаг расчета. Чем меньше шаг расчета, тем слабее будет выражен этот эффект – в такой простой системе мы, вероятнее всего, его даже не заметим. Но, формально, это является ошибкой, поэтому при численном интегрировании системы нескольких разностных уравнений нужно разделять переменные прошлого и следующего шага. Проще всего ввести в программе модели вспомогательные временные переменные и записать все значения (k+1)-го шага в них, а затем, по окончании расчета, переписать эти значения в соответствующие им статические переменные блока. В этом случае операторы присваивания в модели выглядели бы так:

      double x_n,y_n,vy_n; // Временные переменные для (k+1)-го шага
      // Расчет значений (k+1)-го шага
      x_n=x+(DynTime-t0)*vx;    // x[k+1]  = x[k] + h vx[k]
      vy_n=vy-(DynTime-t0)*g;   // vy[k+1] = vy[k] – h g
      y_n=y+(DynTime-t0)*vy;    // y[k+1]  = y[k] + h vy[k]
      // Запись вычисленных значений в переменные блока
      x=x_n;
      y=y_n;
      vy=vy_n;

Здесь мы не теряем значения k-го шага, поскольку вычисленные для (k+1)-го записываем в другие переменные.

В нашем случае система уравнений очень проста, и желаемого результата можно добиться просто переставив местами операторы вычисления y и vy: при этом мы сначала используем переменную vy, в которой хранится vyk, для вычисления нового значения y, и только затем запишем в vy новое значение vyk+1. Если бы система была сложнее и уравнения были бы сильнее связаны друг с другом, перестановка строк программы ничего бы не дала, и пришлось бы вводить временные переменные, как указано выше.

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

      double g=1.62; // Ускорение свободного падения на Луне
      double h;      // Вспомогательная переменная для шага расчета

      h=DynTime-t0;  // Шаг расчета
      t0=DynTime;    // Запоминание текущего времени – на следующем
                     // шаге оно станет предыдущим

      // Вычисления новых значений переменных
      x=x+h*vx;
      y=y+h*vy;
      vy=vy-h*g; // Новое vy вычисляем ПОСЛЕ нового y

Значение горизонтальной скорости тела не меняется, поэтому оператора присваивания для vx в этой модели нет – в течение всего полета тело сохраняет одну и ту же проекцию горизонтальной скорости. У нашего блока нет входов, поэтому мы ничего не сравниваем с признаком ошибки rdsbcppHugeDouble – этому признаку просто неоткуда взяться в переменных блока.

Для тестирования созданной модели можно собрать схему, изображенную на рис. 405. Входов у нашего блока нет, а к его выходам присоединен график, отображающий зависимость двух переменных от времени: выход блока «x» подан на горизонтальную координату графика, выход «y» – на вертикальную. В параметрах блока-планировщика следует задать шаг расчета 0.01 (наше тело будет лететь довольно быстро, поэтому интервал между дискретными моментами времени должен быть небольшим) и время остановки 3 секунды. Синхронизацию с реальным временем можно включить или выключить по желанию. Если запустить расчет, на графике можно будет увидеть траекторию полета тела. Это парабола, как, согласно теории, и должно быть.

Рис. 405. Тестирование модели свободного полета брошенного тела

В этой модели нам пришлось вручную вычислять проекции начальной скорости тела на оси координат и вводить их в качестве начальных значений в структуру переменных блока – это не очень удобно. К тому же, если мы захотим увидеть траекторию полета тела при другой начальной скорости или другом угле броска, нам придется менять значения внутри структуры переменных и компилировать модель заново. Гораздо лучше сделать модуль начальной скорости v0 и ее угол к горизонту α входами блока. Однако, поскольку это – начальные условия, нужно будет написать модель блока таким образом, чтобы значения этих двух входов считывались только в самом начале расчета. Этим мы сейчас и займемся.

Может показаться хорошей идеей считывать модуль и угол начальной скорости со входа блока в реакции модели на событие запуска расчета (RDS_BFM_STARTCALC). Однако, это – не самый лучший выбор по двум причинам. Во-первых, это событие возникает не только при самом первом, но и при повторном запуске расчета. Если остановить расчет, а затем запустить его, реакция модели на запуск будет вызвана снова. Чтобы отличить первый запуск от продолжения расчета, необходимо анализировать параметры события. Во-вторых, и это более важная причина, событие запуска возникает перед тем, как первый такт расчета будет выполнен. Если мы будем подавать значения на входы блока с полей ввода, все будет в порядке: перед расчетом производится начальная передача данных по связям, и значения с полей ввода поступят на входы. Если же начальные значения вычисляются цепочкой каких-либо соединенных блоков (например, значение угла с поля ввода будет подано на вход блока, переводящего радианы в градусы, а с его выхода – уже на вход нашего блока), эта цепочка не успеет вычислить правильное значение начального условия до того, как оно будет считано моделью с входа блока. Действительно, такты расчета, в которых модели всех блоков цепочки будут выполнять вычисления, начнутся уже после того, как блок среагирует на событие запуска расчета и считает начальные значения со своих входов. Таким образом, вместо правильных начальных значений будут считаны значения по умолчанию выходов последнего блока цепочки, непосредственно соединенного с нашим.

Если модель динамического блока (то есть блока, вычисления которого связаны со временем) должна получать начальные значения с входов, лучше всего делать это на первом шаге расчета – именно на первом шаге, а не на первом такте. Шаг расчета – это изменение динамической переменной «DynTime», и на каждый шаг обычно приходится несколько тактов расчета. Число тактов на один шаг задается в параметрах блока-планировщика в поле ввода «дополнительные такты». Кроме того, при первом запуске расчета перед самым первым изменением «DynTime» блок планировщик выполняет так называемые начальные такты, то есть пропускает заданное в его настройках число тактов прежде чем в первый раз увеличивать значение времени. Эти начальные такты нужны как раз для того, чтобы цепочки блоков, вычисляющих начальные значения, успели сработать. Следовательно, чтобы считать с входов правильные начальные значения, модель должна дождаться самого первого изменения переменной «DynTime». Поскольку величина шага расчета модели заранее не известна, для того, чтобы определить момент первого шага лучше всего завести в переменных блока специальный логический флаг с начальным значением 1. Если «DynTime» изменилась, и при этом этот флаг равен единице, значит, это и есть самый первый шаг расчета – модель должна считать начальные условия и сбросить флаг в ноль, чтобы все последующие шаги не считались первыми.

Именно так мы и поступим. Добавим к структуре переменных нашего блока три новых: вещественные входы «v0» и «Alpha», на которые будем подавать модуль начальной скорости и угол броска в градусах соответственно, и внутреннюю логическую переменную «Init» с начальным значением 1, которую будем использовать как флаг первого шага расчета. В начальные значения «vx» и «vy» не будем записывать никаких значений, вычисленных вручную – теперь все будет выполняться автоматически. Новая структура переменных блока будет такой:

Имя | Тип | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | ---
Start | Сигнал | Вход | ✓ | 0
Ready | Сигнал | Выход |  | 0
x | double | Выход |  | 0
y | double | Выход |  | 0
vx | double | Внутренняя |  | 0
vy | double | Внутренняя |  | 0
t0 | double | Внутренняя |  | 0
v0 | double | Вход |  | 0
Alpha | double | Вход |  | 0
Init | Логический | Внутренняя |  | 1

Модель блока изменим следующим образом:

      double g=1.62; // Ускорение свободного падения на Луне
      double h;      // Вспомогательная переменная для шага расчета

      if(DynTime==t0) // Время не изменилось – нет нового
        return;       // шага расчета

      if(Init) // Самый первый шаг расчета (инициализация)
        { if(v0==rdsbcppHugeDouble || Alpha==rdsbcppHugeDouble)
            vx=vy=rdsbcppHugeDouble; // Ошибка на входе
          else
            { double alpha_rad=Alpha*M_PI/180; // В радианы
              // Вычисление начальных проекций скорости
              vx=v0*cos(alpha_rad);
              vy=v0*sin(alpha_rad);
            }
          // Сброс флага инициализации
          Init=0;
        }

      if(vx==rdsbcppHugeDouble || vy==rdsbcppHugeDouble)
        { x=y=rdsbcppHugeDouble; // На входах была ошибка
          return;
        }

      h=DynTime-t0; // Шаг расчета
      t0=DynTime;   // Запоминание текущего времени – на следующем
                    // шаге оно станет предыдущим

      // Вычисление по разностным уравнениям
      x=x+h*vx;
      y=y+h*vy;
      vy=vy-h*g; // Новое vy вычисляем ПОСЛЕ нового y

От предыдущей эта модель отличается тремя добавленными операторами if. Если значение DynTime (текущее время) равно значению t0 (времени на момент последнего расчета), то шаг расчета не совершен – модель вызвана в один из дополнительных или начальных тактов. При этом ни чтения начальных условий, ни вычислений делать не нужно, мы немедленно завершаем реакцию оператором return. Может показаться, что проверка этого условия – лишняя, ведь наша модель вызывается не при изменении входов и не каждый такт, а при изменении динамических переменных, то есть ее единственной динамической переменной «DynTime», и наша реакция, вроде бы, не должна выполняться без изменения времени. Однако, такая проверка позволит нашей модели правильно работать даже если, например, пользователь ошибочно задаст блоку запуск каждый такт – поскольку этот if не сильно усложняет модель, лучше сделать ее устойчивой к таким ошибкам.

Если время изменилось, реакция выполняется дальше, и проверяется значение флага инициализации Init. Если оно не нулевое (а по умолчанию мы дали этой переменной значение 1), мы проверяем входы блока на значение ошибки rdsbcppHugeDouble, и, если оба они не равны этому значению, значение входа Alpha будет переведено в радианы (библиотечные функции sin и cos требуют аргумента в радианах) и по нему и по значению модуля начальной скорости v0 будут вычислены исходные значения проекций скорости vx и vy. Если хотя бы один из входов окажется равным rdsbcppHugeDouble, расчет будет невозможен, и мы присваиваем это же значение и vx, и vy. Затем флаг Init будет сброшен, чтобы инициализация не выполнялась на следующих шагах расчета.

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

Далее располагается уже знакомый нам фрагмент программы, в котором вычисляется шаг расчета h и значения вертикальной скорости и координат на новом шаге – он оставлен без изменений. При данной структуре программы этот фрагмент будет выполнен, только если vx и vy были успешно инициализированы.

Эта модель будет работать следующим образом. В тактах расчета, в которых время не изменилось, она будет завершаться, ничего не вычисляя. Когда время изменится в самый первый раз, сработает проверка «if(Init)…» и будут вычислены начальные значений проекций скорости тела, а флаг Init будет сброшен, после чего будут вычислены новые значения координат и скорости тела, то есть значения на первом шаге. При всех последующих изменениях времени проверка для присвоения начальных условий срабатывать не будет из-за сброшенного флага, и модель будет просто вычислять значения переменных на новом шаге. Если остановить и снова запустить расчет, ничего не изменится – флаг Init останется сброшенным, и модель будет работать так, как будто расчет и не останавливался. Если же сбросить расчет, все переменные блока, включая Init, вернутся к исходным значениям, и при следующем запуске проверка «if(Init)…» снова сработает и снова присвоит проекциям скорости исходные значения.

Для тестирования созданной модели изменим схему: подключим к входам блока «v0» и «Alpha» поля ввода (рис. 406) и введем в них те же значения, которые использовались при проверке прошлой модели: 3 м/с и 50° соответственно. Параметры блока-планировщика оставим теми же. Запустив расчет, мы увидим ту же самую параболу.

Рис. 406. Модель свободного полета с внешним заданием начальных условий

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