Давным-давно мне пришла в голову идея попробовать создать собственный симулятор для развлечения. Но на самом деле я никогда не начинал этим заниматься, потому что меня пугала задача и я думал, что это слишком сложный проект, чтобы я мог его создать в свободное время. Затем, несколько недель назад, я обнаружил simpy. Хотя simpy был не совсем тем, чем я хотел заниматься, прочитав его документацию и примеры, а также познакомившись с концепциями моделирования дискретных событий, я почувствовал, что задача создания симулятор, может быть, не был таким сложным, как я думал раньше.
Моя первая мысль была «Хорошо, все сводится к вычислению набора значений (для каждой моделируемой вещи) внутри большого for
цикла». С этой идеей я решил создать свой собственный инструмент моделирования. Основная идея была настолько проста, что я решил обойтись без каких-либо внешних пакетов, кроме стандартных пакетов библиотек и matplotlib
для построения графиков.
Вот как выглядит мой большой for
цикл (без кода, связанного с созданием графиков):
def run(self):
for i in range(self.cycles):
# run models according to position on execution list
for model_definition in self.execution_list:
model_inputs = self.get_model_inputs(model_definition)
odel_definition["name"]].calculate(**model_inputs)
# update time
self.t.append(self.t[-1] + self.dt)
Вот и все, это монстр, которого я боялся вначале! Не так уж и плохо, не правда ли? Как я уже упоминал, все сводится к вычислению состояний модели и результатов внутри цикла. Конечно, есть намного больше кода, помимо того, что показано выше. И, конечно, это действительно, очень, очень простой симулятор с почти нулевым количеством функций. Он находится в нескольких световых годах от того, чтобы быть похожим на OpenModelica или MATLAB.
По правде говоря, моей главной целью в этом небольшом проекте было фактически создать образец проекта Python, который я мог бы использовать в качестве стандарта для других будущих проектов Python. Следовательно, меня больше интересовали такие вещи, как:
- Как лучше всего структурировать код.
- Использование базовых стандартных библиотек (os, warnings, unittest и т. Д.).
- Настройка и запуск тестов.
- Отладка кода Python.
- Документация по коду.
- Кодирование на Python в целом.
Тем не менее, всего через несколько дней я уже выпустил то, что не стыдно публиковать! Итак, давайте лучше посмотрим, как это работает!
Симулятор класса
У класса симулятора есть только несколько обязанностей:
- Загружайте, запускайте и отслеживайте все моделируемые модели.
- Следите за временем.
- Инициализируйте графики.
Модели загружаются из папки, содержащей файлы JSON с настройками и конфигурацией для каждой из моделей, которые будут использоваться во время моделирования. Пример настроек модели схемы RC (резистор-конденсатор) показан ниже:
{
"name": "rc",
"class": "RC",
"order": 1,
"params": {
"resistance": 2,
"capacitance": 0.4,
"charge": 0.0
},
"inputs": {
"Vin": {
"model": "sig",
"variable": "current_value"
}
},
"plot": {
"Vc": {}
}
}
После загрузки моделей класс Simulator запускает большой for
цикл для вычисления смоделированных значений и обновления графиков.
Классы моделей
Подпакет модели тренажера содержит модули, используемые для моделирования и моделирования различных компонентов. У этих модулей есть две обязанности:
- Инициализируйте модель.
- Сохраните переменные ввода, состояния и вывода.
- Рассчитайте состояние компонента на основе состояния и входных переменных.
Пример для модели RC показан ниже.
class RC:
""" A class used to model an RC (resistor-capacitor) circuit """
def __init__(self, dt, resistance, capacitance, charge):
# constants
self.dt = dt
self.R = [resistance]
self.C = [capacitance]
# state variable
self.Q = [charge]
self.Q1 = [charge]
# inputs
self.Vin = [None]
# other
self.Vr = [None]
self.Vc = [None]
self.i = [None]
def calculate(self, Vin):
""" Calculates the next value and adds it to the memory """
self.Vc.append(self.Q[-1]/self.C[-1])
self.Vr.append(Vin - self.Vc[-1])
self.i.append(self.Vr[-1]/self.R[-1])
# update state variables
self.Q.append(self.dt*((1/self.R[-1])*(Vin-(1/self.C[-1])*self.Q1[-1]))+self.Q1[-1])
self.Q1.append(self.Q[-1])
# save input just for recording
self.Vin.append(Vin)
# return the charge
return self.Q[-1]
Как показано выше, атрибуты этого класса отслеживают переменные компонента, а метод вычисления использует числовое дифференцирование для вычисления переменных компонента.
Пример моделирования
Загрузив три файла JSON в папку конфигурации, я могу смоделировать три разных компонента:
- Генератор сигналов (специальный класс, который выводит значения на основе своего файла настроек).
- RC-цепочка (резистор-конденсатор).
- Схема RL (резистор-индуктор).
И эти компоненты подключаются (через файл настроек), как показано ниже:
Схема нарисована с помощью circuitlab.
Результаты моделирования для 20-секундного прогона показаны ниже. Фиолетовый сигнал - Vin - представляет входное напряжение в цепи, и я специально сделал его по странной кривой, чтобы увидеть реакцию каждого компонента.
Что дальше
В будущем я хотел бы добавить к этому проекту несколько вещей:
- Создайте достойный графический интерфейс, чтобы упростить использование симулятора.
- Добавляйте больше интересных моделей (электродвигатели, компрессоры, турбины, клапаны и т. Д.).
Репозиторий
Этот до тупо простой код симулятора можно найти здесь.
Первоначально опубликовано на сайте fabiomolinar.com 24 июля 2019 г.