Описание пользователя
Глава 3. Использование стандартных модулей автокомпиляции
§3.6. Принципы создания автокомпилируемых моделей блоков
§3.6.4. Моделирование длящихся во времени процессов
Рассматривается создание динамических блоков – блоков, моделирующих какие-либо процессы, протекающие во времени и описываемые дифференциальными или разностными уравнениями. Приводятся примеры решения конкретных задач, описывается решения проблем с устойчивостью счета.
§3.6.4.1. Общие принципы численного моделирования непрерывных процессов
Описываются общие принципы перехода от описания процесса дифференциальными уравнениями к разностным уравнениям, которые можно закладывать в модель блока.
Достаточно часто приходится моделировать процессы, в которых величины, описывающие их, изменяются во времени по каким-либо законам. Случаи, когда для каждой величины известна формула закона ее изменения от времени, встречаются редко – как правило, известны связи параметров и состояний процесса друг с другом (причем эти связи могут быть довольно сложными), в результате действия которых процесс идет тем или иным образом. К тому же, на процесс могут воздействовать различные внешние факторы, законы изменения которых не известны в принципе.
Рассмотрим простой пример: некоторый объект двигается по прямой с заданной скоростью. Требуется определить положение объекта в заданный момент времени. Из курса физики средней школы известно, что при постоянной скорости положение объекта в любой момент времени определяется формулой
![]()
где
| x0 | – начальное положение объекта на прямой, |
| v | – скорость объекта, |
| t | – время. |
Если скорость не постоянна, эту формулу использовать нельзя. В этом случае приходится описывать движение объекта дифференциальным уравнением, то есть уравнением с участием производных. Скорость объекта v – это производная его координаты x по времени t (то есть, фактически, «быстрота изменения координаты»). При нулевом значении времени объект находится в положении x0. Значит, можно записать дифференциальное уравнение

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

Для некоторых зависимостей скорости от времени v(t) этот интеграл может быть вычислен аналитически. Но, к сожалению, значение скорости в каждый момент времени чаще всего само получается в результате решения некоторого набора уравнений. Или, что тоже возможно, оно поступает снаружи – например, в схеме расположен блок-рукоятка для задания скорости, и пользователь все время двигает ее, изменяя скорость так, как ему хочется. В этом случае приведенное выше дифференциальное уравнение приходится решать одним из известных методов численного интегрирования, переходя от непрерывно текущего времени к дискретному. Ниже мы рассмотрим примеры такого решения.
Для численного решения дифференциальных уравнений мы заменяем непрерывное время дискретным, изменяющимся небольшими шагами. Чем меньше будут эти шаги, тем ближе будет дискретное время к непрерывному, и тем ближе численное решение уравнения будет к аналитическому. Шаги изменения дискретного времени могут быть как постоянными, так и изменяющимися. Для простоты будем считать их постоянными – тем более, что стандартный блок-планировщик, обеспечивающий схему текущим значением времени через динамическую переменную «DynTime» (см. §3.6.3.1), изменяет это время с заданным постоянным шагом. Перейдя к такому дискретному времени, мы заменяем дифференциальные уравнения разностными, которые позволяют вычислить значение некоторой переменной на следующем шаге по значениям переменных (как данной, так и, возможно, нескольких других) на одном или нескольких предыдущих шагах. Существует множество различных методов численного интегрирования, то есть способов, позволяющих получить разностные уравнения из дифференциальных, но мы будем использовать самый простой из них – метод Эйлера. Рассмотрим его на примере приведенной выше задачи определения координаты объекта, скорость движения которого задается извне.
Переходя к дискретному времени, мы заменяем непрерывное время t набором дискретных значений t0, t1, t2, … tk, tk+1, … В каждый из уже прошедших моментов времени вплоть до текущего момента tk нам должны быть известны значения скорости v0…vk – скорость задается снаружи исследуемой нами системы, мы можем запоминать всю историю ее изменения, если это необходимо, и всегда знаем текущее ее значение. Мы не можем его вычислить, мы можем только получить его откуда-нибудь – например, оно может приходить по связи на вход нашего блока от каких-либо других блоков, от рукоятки, которую двигает пользователь, от реального аппаратного датчика скорости и т.п. Наша задача – получить выражение координаты объекта xk+1 в следующий момент времени tk+1, зная историю движения этого объекта, то есть, в общем случае, набор значений его координат x0, x1, … xk и скоростей v0, v1, … vk (мы увидим, что в простейшем случае достаточно иметь только предыдущие значения координаты и скорости, то есть xk и vk). Будем считать, что на интервале времени tk…tk+1 скорость объекта не меняется и равна vk. Скорость v – это производная координаты x по времени t. Поскольку на указанном интервале она постоянна, можно записать следующее выражение:
![dx/dt{при t=t[k]...t[k+1]} = (x[k+1]-x[k])/(t[k+1]-t[k])=v[k]](../img/Form_ac_4.png)
Действительно, при постоянной скорости на интервале tk…tk+1 эта скорость равна отношению изменения координаты к изменению времени. Отсюда можно получить необходимое нам выражение для вычисления xk+1:
![]()
Это и есть разностное уравнение, полученное по методу Эйлера. Для вычисления очередного значения координаты xk+1 в момент времени tk+1 нам нужно знать прошедший с последнего вычисленного отсчета интервал времени tk+1−tk, прошлое значение координаты xk и скорость ее изменения на момент начала интервала vk. В общем виде, если у нас есть дифференциальное уравнение первого порядка (то есть с одной производной) вида

где x, y, z – некоторые переменные, t – время, а f – известная функция, то разностное уравнение, полученное по методу Эйлера для этого дифференциального, будет выглядеть так:
![]()
При этом функция f может содержать внутри себя любые нелинейные функции или даже сложные алгоритмы вычисления. Нелинейность функции f, которая могла бы явиться препятствием для получения аналитического решения дифференциального уравнения, не помешает его численному решению. Нужно только правильно подобрать шаг расчета, то есть интервал между соседними отсчетами времени, чтобы моделирование процесса давало удовлетворительную точность (влияние шага расчета на численное решение показано в примере в §3.6.4.3).
Если дифференциальное уравнение имеет больший порядок, надо заменить его на систему дифференциальных уравнений первого порядка любым известным способом, и привести эту систему в нормальную форму Коши, то есть в форму, в которой в левой части каждого уравнения находится первая производная, а в правых частях производных нет. После этого для каждого уравнения получившейся системы методом Эйлера записывается разностное. Допустим, у нас есть система дифференциальных уравнений в форме Коши:

где
| y1…yN | – вычисляемые переменные (выходы и внутренние), |
| x1…xM | – поступающие снаружи переменные (входы), |
| f1…fN | – известные функции, |
| y01…y0N | – начальные значения вычисляемых переменных, |
| t | – время. |
При помощи метода Эйлера из нее можно получить следующую систему разностных уравнений:
![y[i,k+1]=y[i,k]+(t[k+1]-t[k])fi(y[1,k],...,y[N,k],x[1,k],...,x[M,k],t[k])](../img/Form_ac_9.png)
Для удобства записи номер отсчета мы здесь перенесли в верхний индекс: yk1 – это не «y1 в степени k», это «k-й отсчет переменной y1».
Вернемся к задаче вычисления координат объекта, движущегося с заданной скоростью, разностное уравнение для которой мы уже получили. Это разностное уравнение можно практически без изменений перенести в модель блока. Есть только одна сложность: в уравнении нам необходим интервал времени между прошлым отсчетом времени tk и новым отсчетом tk+1. Если новое значение времени tk+1 можно взять из динамической переменной «DynTime», в которую его постоянно записывает блок-планировщик (см., например, §3.6.3.1), то прошлый отсчет tk в явном виде нам взять неоткуда. Придется в модели постоянно запоминать текущее значение времени в какой-нибудь внутренней переменной блока: когда планировщик изменит «DynTime» и наша модель запустится, в этой внутренней переменной окажется значение времени до последнего изменения, что нам и требуется.
Создадим блок, входом которого будет задаваемая скорость «v», а выходом – координата объекта «x». Прошлое значение времени для вычисления шага расчета tk+1−tk будем хранить во внутренней вещественной переменной «t0». Переменную для начального значения координаты объекта создавать не будем: начальное значение можно записывать в значение по умолчанию переменной «x» (далее в §3.6.4.2 будет приведен пример модели, в которой начальные условия считываются из отдельных входов блока). Таким образом, структура переменных нашего блока будет такой:
| Имя | Тип | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|
| Start | Сигнал | Вход | ✓ | 0 |
| Ready | Сигнал | Выход | 0 | |
| v | double | Вход | 0 | |
| x | double | Выход | 0 | |
| t0 | double | Внутренняя | 0 |
В отличие от многих моделей, описанных ранее, здесь мы не даем сигналу запуска блока «Start» единичное начальное значение и не включаем флажок «» у входа блока «v»: наша модель работает при изменении времени, и запускать ее при изменении входов бессмысленно. Пока время не изменится, новое значение координаты вычислено не будет, поскольку при одинаковых tk+1 и tk разностное уравнение вырождается в «xk+1 = xk+1», то есть координата объекта не изменяется – «лишний» запуск модели до приращения времени ничего не испортит, но и не даст ничего для расчета. Следует учитывать, что это верно для рассматриваемого здесь метода Эйлера, при использовании других методов численного интегрирования может оказаться важным не допускать запуска модели без изменения времени. В качестве начального значения переменной «x» мы записали ноль – считаем, что объект движется из начала координат – но можно ввести любое другое начальное значение. Начальное значение переменной «t0» обязательно должно быть нулем – на первом шаге предыдущее значение времени равно нулю.
Создадим новый блок с автокомпилируемой моделью, зададим для него запуск по сигналу и введем в редакторе модели указанную выше структуру переменных. Присоединим блок к динамической переменной «DynTime». Параметры модели, которые она получила по умолчанию, изменять не будем – в результате в нашу модель будет автоматически добавлена проверка существования переменной «DynTime» и взведение сигнала запуска при ее изменении, что нам и нужно.
На вкладке «» редактора необходимо ввести программу, соответствующую полученному нами разностному уравнению
![]()
С учетом того, что предыдущее значение времени мы решили хранить во внутренней переменной «t0», модель будет выглядеть так:
x=x+(DynTime-t0)*v; // Разностное уравнение t0=DynTime; // Запоминание предудущего отсчета времени
Можно видеть, что формула для вычисления x в точности соответствует разностному уравнению. На каждом шаге расчета в DynTime будет находиться новое значение времени, для которого нужно вычислить новую координату x, а в t0 – значение времени, запомненное при прошлом вычислении (это обеспечивается второй строчкой программы).
Для тестирования созданного блока соберем схему, изображенную на рис. 401. В ней к входу скорости «v» присоединено поле ввода, а выход координаты объекта «x» подан на график. На второй график подано значение скорости с поля ввода, которое мы будем изменять в процессе расчета. В параметрах блока-планировщика задан шаг расчета 0.1 и время остановки 10. Синхронизация с реальным временем включена, иначе мы не успеем изменить скорость на входе блока за время расчета (можно даже замедлить расчет, увеличив в параметрах планировщика множитель задержки). На рисунке изображен результат расчета, в процессе которого сначала значение на входе блока равнялось двум, после третьей секунды оно было изменено на ноль, после пятой – на минус один (это видно на нижнем графике). В результате в течение первых трех секунд расчета координата объекта увеличивалась, затем он остановился (горизонтальная линия на верхнем графике), а затем, после пятой секунды, двинулся в обратном направлении с вдвое меньшей, чем в начале, скоростью.
Рис. 401. Тестирование вычислителя координаты объекта,
движущегося с задаваемой скоростью
Фактически, мы только что создали простейшую модель интегратора – линейного динамического блока, часто используемого при моделировании различных процессов. Из таких блоков можно собирать большие схемы, численно решающие различные системы дифференциальных уравнений, но, во многих случаях, гораздо удобнее закладывать всю систему уравнений в одну модель блока – такие примеры будут рассмотрены далее.
Рис. 402. Электрическая схема RC-цепи
Разумеется, численно моделировать можно не только процессы в механических системах, вычисляя координаты, скорости и ускорения различных объектов или деталей механизмов. Описанным выше способом можно моделировать любые процессы, которые описываются системами дифференциальных уравнений: электрические, термодинамические и т.п. Рассмотрим, например, электрические процессы, протекающие в стандартной RC-цепи (рис. 402) и блок, моделирующий эти процессы.
Цепь представляет собой последовательное соединение резистора сопротивлением R и конденсатора емкостью C, подключенное к источнику напряжения uВХ(t), значение которого будет задаваться на входе нашего блока. Напряжение на конденсаторе uC(t) будет выходом нашего блока. При изменении напряжения uВХ конденсатор будет заряжаться или разряжаться – именно процесс его заряда и разряда мы и будем моделировать.
Сначала составим дифференциальные уравнения, описывающие электрическую схему. Резистор и конденсатор соединены последовательно, поэтому сила тока, протекающего через них, одинакова (ток через вольтметр, подключенный к конденсатору для измерения uC, будем считать пренебрежимо малым):
![]()
Эта сила тока является функцией времени t. Сумма падений напряжения на резисторе и конденсаторе будет равна входному напряжению цепи:
![]()
Сила тока через конденсатор равна его емкости, умноженной на производную напряжения на нем по времени:

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

Таким образом,

Это и есть дифференциальное уравнение, описывающее процессы в RC-цепи. Переписав его в нормальной форме Коши, то есть переместив производную в левую часть уравнения, получим:

Для этого дифференциального уравнения должны быть заданы начальные условия – в данном случае, напряжение на конденсаторе в нулевой момент времени. Для простоты будем считать, что конденсатор исходно разряжен, то есть uC(0)=0. Переходя к дискретному времени и заменяя по методу Эйлера производную напряжения на отношение его изменения за шаг расчета к изменению времени за этот же шаг, получим:
![duC/dt{при t=t[k]...t[k+1]} = (uC[k+1]-uC[k])/(t[k+1]-t[k]) = 1/(RC) (uВХ[k]-uC[k])](../img/Form_ac_16.png)
Таким образом, мы получили следующее разностное уравнение для вычисления очередного (k+1-го) значения uC:
![uC[k+1]=uC[k]+(t[k+1]-t[k]) (uВХ[k]-uC[k])/(RC)](../img/Form_ac_17.png)
По этому уравнению легко будет написать модель блока.
Создадим блок, входами которого будет напряжение источника uВХ(t) «Uin» в вольтах, сопротивление резистора «R» в омах и емкость конденсатора «C» в фарадах, а выходом – напряжение на конденсаторе «Uc» в вольтах. Конечно, удобнее было бы задавать емкость конденсатора в каких-нибудь более часто используемых единицах – например, микрофарадах – но мы, для упрощения модели, будем все задавать в единицах СИ. Прошлое значение времени для вычисления шага расчета будем, как и в предыдущем примере, хранить во внутренней вещественной переменной «t0». Начальное напряжение на конденсаторе (мы решили считать его исходно разряженным) запишем в значение по умолчанию переменной «Uc». Структура переменных нашего блока будет такой:
| Имя | Тип | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|
| Start | Сигнал | Вход | ✓ | 0 |
| Ready | Сигнал | Выход | 0 | |
| R | double | Вход | 1 | |
| C | double | Вход | 1 | |
| Uin | double | Вход | 0 | |
| Uc | double | Выход | 0 | |
| t0 | double | Внутренняя | 0 |
Начальные значения входов «R» и «C» мы, на всякий случай, сделаем единицами: если мы забудем подключить к этим входам поля ввода, деление на их произведение на вызовет ошибки.
Создадим новый блок с автокомпилируемой моделью, зададим для него запуск по сигналу и введем в редакторе модели указанную выше структуру переменных. Присоединим блок к динамической переменной «DynTime». Параметры модели изменять не будем – они останутся установленными по умолчанию, и наш блок будет автоматически запускаться при каждом изменении времени (то есть на каждом шаге расчета). На вкладке «» редактора введем программу, соответствующую полученному выше разностному уравнению для uC:
Uc+=(DynTime-t0)*(Uin-Uc)/(R*C); t0=DynTime;
В первой строчке мы вычисляем новое значение Uc на момент времени tk+1, во второй – запоминаем текущее время в t0, чтобы на следующем шаге расчета вычесть его из изменившегося времени и получить величину шага. Для упрощения примера в этой модели мы не сравниваем входы блока Uc, R и C с признаком ошибки rdsbcppHugeDouble и не проверяем R и C на равенство нулю.
Для тестирования модели соберем схему, изображенную на рис. 403. Зададим сопротивление резистора «R» равным 2 кОм (2000 Ом), емкость конденсатора «C» – 1000 мкФ (0.001 Ф). Шаг расчета в параметрах блока-планировщика зададим равным 0.1 с, а время остановки – 10 с. Подадим на вход «Uin» напряжение 10 В и запустим расчет – на графике можно будет увидеть заряд конденсатора. Когда расчет остановится, подадим на вход блока ноль вольт и снова запустим расчет (время остановки при этом автоматически удвоится, то есть станет равным 20 с) – на графике будет видно, как разряжается ранее заряженный конденсатор.
Рис. 403. Тестирование блока моделирования RC-цепи (заряд конденсатора
напряжением 10 В в течение 10 секунд, затем разряд в течение еще 10 секунд)