petrochemРазведка
АктуальноеСрочноНовости и поискДайджестИсследованияГраф связейИнтерактивО проекте
поиск/
petrochem.insight
о проекте

Платформа технологической разведки и отраслевой агрегатор новостей нефтехимической промышленности: технологии, проекты, оборудование, рынки, безопасность, моделирование.

разделы
  • Актуальное (дайджест)
  • Новости и поиск
  • Граф связей
  • О проекте
источники

Данные агрегируются из открытых отраслевых источников. Мнение редакции может не совпадать с мнением источников.

© 2026 Нефтехимическая разведка · v0.1.0-dev

built with next.js · directus · pgvector

Исследования/Солвер
исследование · моделирование технологических схем

Солвер, который решает всё, что может: принцип HYSYS-класса на примере Sim42

Непоследовательно-модульный расчёт технологических схем: идея — для специалиста по моделированию, устройство — для разработчика, история проекта — по открытым источникам.

Вадим Петрусенко по материалам открытых источников и анализа исходного кода20 октября 2026 · ≈ 40 мин чтения
режим чтения
Лаборатория солвера
содержание
  1. Аннотация
  2. 1. Введение
  3. 2. История: от Hyprotech к Sim42
  4. 2.1 Калгарийская школа
  5. 2.2 Рождение Sim42
  6. 2.3 Откуда идеи солвера
  7. 2.4 Лицензия
  8. 2.5 После 2004 года
  9. 3. Принцип для специалиста
  10. 4. Разбор расчёта схемы
  11. 5. Устройство ядра
  12. 6. Сравнение и ограничения
  13. 7. Практические следствия
  14. 8. Заключение
  15. 9. Методика и ограничения
  16. Источники
  17. Глоссарий
режим чтения

аннотация

Рассматривается принцип работы солвера технологических схем класса non-sequential modular: класса, к которому в литературе относят HYSYS[1] и к которому принадлежит открытый симулятор Sim42. Sim42 — магистерский проект Университета Калгари (2001–2003); в разработке его солвера определяющую роль сыграл Крейг Моррис, по собственному свидетельству ранее создавший симулятор Hysim в компании Hyprotech[1, 3, 4]. На примере Sim42 описаны ключевые механизмы: хранение значения вместе со статусом происхождения, распространение частичной информации вперёд и назад по схеме, очерёдность решения аппаратов по стеку LIFO, инвалидация устаревших результатов без графа зависимостей (двухпроходная схема «забывание → решение») и итерации по рециклу методом Бройдена. Изложение ведётся на двух уровнях — для специалиста по моделированию (степени свободы, порядок ввода, диагностика) и для разработчика (структуры данных и алгоритмы). К статье прилагается интерактивная лаборатория: собственная реализация алгоритма, проверенная автоматическими тестами. История проекта восстановлена по открытым источникам; различаются подтверждённые факты и гипотезы. Исходный код Sim42 анализировался статически, без запуска[12, 13].

Abstract. The paper explains how a non-sequential modular flowsheet solver — the class to which HYSYS is assigned in the literature — works, using the open-source Python simulator Sim42 (University of Calgary, 2001–2003) as a worked example. We describe value-with-status storage, bidirectional propagation of partial information, LIFO dispatch of unit operations, dependency-graph-free invalidation (a two-pass “forget → solve” scheme) and recycle convergence with Broyden’s method. The text has two layers (for the simulation practitioner and for the developer) and is accompanied by an interactive lab, an independent re-implementation of the algorithm verified by automated tests. The history of the project is reconstructed from open sources, with confirmed facts distinguished from hypotheses.

Ключевые слова: солвер технологических схем; непоследовательно-модульный подход; распространение ограничений; степени свободы; рецикл; метод Бройдена; Sim42; HYSYS.

1Введение: где задавать данные и в каком порядке считать

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

В литературе выделяют два основных подхода к решению схемы[1, §1.1.1]. В уравнение-ориентированном (equation-oriented) подходе уравнения всех аппаратов собираются в одну систему и решаются одновременно методами типа Ньютона. В модульном (modular) подходе схема решается аппарат за аппаратом; модульные симуляторы делятся на последовательные (sequential modular), где расчёт идёт строго от сырья к продукту, и непоследовательные (non-sequential modular), способные распространять информацию вперёд и назад, так что пользователь может задать данные в любой точке схемы. Определение последнего типа диссертация, на которую опирается эта статья, связывает с работой Svrcek и соавторов 1984 года[1, §1.1.1].

Последовательно-модульныйsequential modularБлок 1Блок 2Блок 3рецикл: итерации по всей схемерасчёт строго по ходу потока;данные задаются на входахвыход задать нельзя без подбораУравнение-ориентированныйequation-orientedодна система уравненийF(x) = 0Ньютон / квази-Ньютонрецикл — часть решенияспецификации — где угодно,если степени свободы сошлисьтрудно инициализировать и отлаживатьНепоследовательно-модульныйnon-sequential modular (Sim42, класс HYSYS)Блок 1Блок 2Блок 3информация идёт в обе стороныстек аппаратов (LIFO)решается всё, что можно решитьпо уже известным даннымрецикл — только где без него нельзя
Рисунок 1. Три подхода к решению технологической схемы (схема автора; классификация по [1, §1.1.1]).
Таблица 1. Свойства подходов к решению схемы (по [1], §1.1.1)
ПодходДостоинстваНедостатки
Уравнение-ориентированныйНовые аппараты добавляются описанием уравнений; рецикл — часть решения; быстрое повторное решение; данные можно задавать где угодно при выполнении степеней свободыСложно решить в первый раз и инициализировать; при сбое решателя результата нет вовсе; трудно отлаживать
Последовательно-модульныйМалые системы уравнений, проще инициализация; частичный результат есть, даже если вся схема не решенаДля спецификаций на выходе нужны алгоритмы подбора; рецикл — итерации по всей схеме
Непоследовательно-модульныйТо же, плюс свобода задавать свойства на выходах и получать требования к сырью расчётомМедленен для динамики; рецикл сходится итерациями по всей схеме

Строки сформулированы по перечню преимуществ и недостатков в тексте диссертации; для непоследовательно-модульных симуляторов добавлено отличие от последовательно-модульных.

Коммерческие солверы закрыты, и разобраться, как именно «думает» непоследовательно-модульное ядро, по документации обычно нельзя. Этим объясняется выбор объекта изучения: Sim42 — открытый симулятор, исходный код которого доступен[7], а принцип работы солвера описан в диссертации его автора[1] и подробно разобран в аналитических материалах по коду[12, 13]. Это позволяет показать механизм «изнутри» и на конкретных числах.

Как читать статью

Текст устроен в два слоя. Для специалиста по моделированию — разделы 3 и 4: идея, степени свободы, порядок ввода, диагностика. Для разработчика — раздел 5: структуры данных и алгоритмы; он свёрнут по умолчанию, режим чтения переключается вверху страницы. Интерактивные модели встроены в разделы 3, 4 и 5 и доступны в полном окне на отдельной странице.

2История: от Hyprotech к Sim42

Что здесь подтверждено, а что нет

История восстановлена по открытым источникам с указанием их типа. Подтверждено родство людей и идей: автор солвера Sim42 пишет, что создавал Hysim; в диссертации HYSYS и Sim42 отнесены к одному классу солверов. Не подтверждено, что Sim42 использует или повторяет код HYSYS: ни диссертация, ни сайт проекта этого не утверждают. Поэтому в статье Sim42 рассматривается как «пример принципа класса HYSYS», а не как «солвер HYSYS».

2.1. Калгарийская школа и HYSYS

Компания Hyprotech создана исследователями Университета Калгари; профессор В. Свэрчек (W. Y. Svrcek) значится партнёром Hyprotech с момента её учреждения в 1976 году[9, 10]. Продукты компании — HYSIM, затем HYSYS (справочник по HYSYS версии 1.1 издан в 1996 году). В мае 2002 года Hyprotech приобрела AspenTech; впоследствии, по решению Федеральной торговой комиссии США, права на часть активов, включая исходный код HYSYS, были переданы Honeywell, на базе чего появился UniSim[9]. Цены и даты сделок в источниках расходятся, подробности для сути статьи несущественны.

Для истории солвера важнее другое: в диссертации по Sim42 HYSYS перечислен среди «непоследовательно-модульных» симуляторов (Канада), а само понятие со ссылкой на Svrcek et al. (1984)[1, §1.1.1–1.1.2]. Оригинал этой работы найти в открытых источниках не удалось, поэтому ссылка приводится «через диссертацию».

2.2. Рождение Sim42 (2001–2003)

Хронологию проекта дают две группы первоисточников: страница «The History of Sim42» на сайте проекта (архивная копия от 9.12.2002)[2] и благодарности в диссертации[1]. В мае 2001 года Raúl Cota (Рауль Кота) начал в Университете Калгари магистерскую работу — создание симулятора с открытым кодом на Python; руководителями были Marco Satyro, Bill Svrcek и Brent Young[1, 2].

Таблица 2. Хронология проекта Sim42 по первоисточникам
ДатаСобытиеИсточник
Май 2001Cota начинает магистерский проект: открытый симулятор на Python[2]
Лето 2001Каркас симулятора: структура аппаратов, порты и их соединения; принцип «ядро независимо от интерфейсов»[2]
Лето–осень 2001Satyro (VMG) передаёт термо-пакет; знакомит с проектом Craig Morris — тот берётся написать «более изощрённый солвер», размещает репозиторий и рассылку[2]
Осень 2001VMG решает использовать Sim42 как ядро коммерческой VMGSim (Excel/Visio); передаёт систему единиц; к проекту присоединяется Yau Kun Li[2]
Зима 2001/02Новые аппараты, работающая колонна, тестовый набор; рабочее имя Awesim занято — Cota предлагает «Sim42»; в конце марта 2002 получен домен sim42.org[2]
Лето 2002GUI-версия командной строки, первый сайт; создаётся Sim42 Software Foundation (общество в Британской Колумбии) — хранитель лицензии и имени[1, 2]
Осень 2002Satyro пишет руководство; сайт на 8–9.12.2002 зафиксирован в веб-архиве[2, 5]
Апрель 2003Диссертация Cota на степень M.Sc.[1]
1976Hyprotech
основана исследователями U Calgary [9, 10]
1984Svrcek et al.
определение non-sequential [1]
1996HYSYS
издан справочник версии 1.1 [9]
2001Sim42
начало проекта Cota, май [2]
2002Фонд · лицензия
Sim42 Software Foundation (BC); AspenTech покупает Hyprotech [2, 9]
2003Диссертация
защита M.Sc., апрель [1]
2007–15OllinTS
SourceForge, GPLv2 [8]
2011GitHub
импорт v2.0.0.0, NumPy [7]
Рисунок 2. Хронология: от основания Hyprotech до импорта Sim42 на GitHub. Янтарным отмечены события проекта Sim42 (схема автора).

Участники проекта и их вклад по тем же источникам[1, 2, 3, 4]: Raúl Cota — исходная архитектура (аппараты, порты, интерфейс к термодинамике), графическая версия командной строки, испанские сообщения; Craig Morris — солвер, колонна, контроллеры и скриптовые аппараты, первый командный интерфейс, хостинг репозитория; Marco Satyro — термодинамика (через VMG), руководство пользователя, научное руководство; Bill Svrcek и Brent Young — научное руководство; Yau Kun Li — насосы с кривыми, конверсионные реакторы, гипотетические компоненты; Andrew Liu и Rajeev Agarwal — код и спецификации аппаратов; Patrick O’Brien — интерпретатор PyCrust; Jose-Henrique Pinto — переводы сообщений и FAQ. Систему единиц передала компания VMG[2].

Главная страница сайта sim42.org, архивная копия от 8 декабря 2002 года
Рисунок 3а. Главная страница sim42.org, 8.12.2002: «Could it be the answer to everything in your simulation universe?» Снимок веб-архива[5], уменьшен.
Страница «The History of Sim42» на sim42.org, архивная копия от 9 декабря 2002 года
Рисунок 3б. Страница «The History of Sim42», 9.12.2002 — основной первоисточник по хронологии[2]. Снимок веб-архива, уменьшен.

2.3. Откуда пришли идеи солвера

Автор солвера Craig Morris в профиле на сайте проекта пишет, что «когда-то написал симулятор Hysim» и покинул Hyprotech за десять с лишним лет до 2002 года; об устройстве будущего солвера у него были «довольно необычные идеи», и он вызвался написать солвер для проекта Cota[3]. Satyro, в свою очередь, отмечает, что Sim42 «унаследовал» от Morris двунаправленную передачу информации, вложенность аппаратов и командный язык[4]. Эти свидетельства самоописательны (профили участников, написанные ими самими), но совпадают с описанием солвера в диссертации и по коду[1, 13].

Следовательно, правомерно сказать: принцип непоследовательно-модульного солвера, реализованный в Sim42, — того же класса, что и в HYSYS, и создан в среде и руками людей, связанных с Hyprotech. Утверждать идентичность алгоритмов или происхождение кода HYSYS в Sim42 открытые источники оснований не дают.

2.4. Лицензия и сообщество

Код Sim42 распространялся по «SIM42 Software License, Version 1.0» (© 2002 SIM42 Software Foundation)[6]. Сайт и FAQ называли её BSD-подобной: разрешены использование и изменение без обязательной передачи изменений, что важно для компаний, добавляющих собственные модели[1, 5]. Текст лицензии содержит условия атрибуции: в документации распространяемого продукта должно быть указание на разработку SIM42 Software Foundation; производные продукты нельзя называть SIM42 без письменного разрешения[6]. В 2008 году участники проекта OpSim сочли эти условия несовместимыми с GPL и разработали собственный симулятор «с нуля», используя Sim42 как «источник идей»[11] — это мнение участников, а не юридическое заключение.

Атрибуция

Статья описывает идеи программного обеспечения, разработанного участниками проекта Sim42 и SIM42 Software Foundation (http://www.sim42.org/). Модели лаборатории — самостоятельная реализация алгоритма, не содержащая кода Sim42 и не распространяемая под именем SIM42.

2.5. После 2004 года

Дальнейшую судьбу проекта в открытых источниках удаётся проследить лишь фрагментарно. Домен sim42.org с 2013 года в веб-архиве представлен страницами-«парковками». Дистрибутив версии 2.0.0.0 сохранился на SourceForge в проекте OllinTS (зарегистрирован в 2007 году, последнее обновление — 2015; лицензия GPLv2) — это термодинамический сервер, который создавался «для завершения Sim42»[8]. В 2011 году этот дистрибутив был импортирован в репозиторий GitHub, после чего автором импорта начата конвертация с устаревшего расширения Numeric на NumPy[7]; последний коммит — ноябрь 2011 года. Остатки обращений к Numeric в отдельных модулях, отмеченные в аналитических материалах[12, 13], — следы этой незавершённой миграции. Признаков активного сопровождения в открытых источниках не обнаружено.

3Принцип для специалиста по моделированию: «решает всё, что может»

3.1. Главная идея

В классическом последовательном симуляторе пользователь подаёт сырьё, затем считается первый аппарат, потом второй и так далее. Если нужно «задать то, что на выходе», схему приходится перекраивать вручную. Солвер класса Sim42 работает иначе. Представьте большую таблицу судоку: каждая клетка — параметр потока (температура, давление, расход, состав), а каждый аппарат — набор правил («сумма на входе равна сумме на выходе»). Как только в какой-то клетке появляется число, все правила, которым хватает данных, срабатывают и заполняют соседние клетки — в любую сторону[1, 12].

Главная идея

Солвер не «считает по цепочке слева направо», а постоянно отслеживает всё, что известно, и немедленно вычисляет всё, что можно вычислить — вперёд и назад по схеме. Итерации по рециклу нужны только там, где без них не обойтись[12].

  • Информация идёт в обе стороны. Если известны состав и расход смеси на выходе смесителя и один из двух входов, второй вход вычисляется как разность.
  • Порядок ввода не важен. Можно сначала задать давление на выходе, потом температуру на входе — результат тот же.
  • Расчёт идёт сразу после каждого ввода (если не включён режим паузы), поэтому схему можно собирать и «чувствовать» в интерактивном режиме.
  • Часто рецикл считать не нужно. Авторы отмечают, что за счёт распространения частичной информации во все стороны многие сложные задачи решаются без итераций по рециклам[5].
  • Защита от переопределения. Если одно значение получено из двух источников и они не совпали, программа не молчит, а сообщает о противоречии.

3.2. Из каких «кирпичиков» состоит модель

Аппарат (unit operation) — любой объект схемы: смеситель, нагреватель, колонна, насос, контроллер. В Sim42 потоки — тоже аппараты, а не отдельная сущность; аппараты можно вкладывать друг в друга (теплообменник собран из нагревателя и охладителя)[1]. Порт (port) — «розетка» аппарата, через которую течёт информация; бывают материальные порты (T, P, расход, состав), энергетические и сигнальные (одно число — перепад давления, управляющий сигнал). Направление порта — это направление потока вещества, а не порядок расчёта: информация через порт ходит в обе стороны.

Каждое значение хранится вместе со статусом происхождения — откуда оно взялось. Именно статус позволяет программе решать, что можно перезаписывать, а что нельзя.

Таблица 3. Статусы значений переменной (по [12], [13])
СтатусЧто означаетВ интерфейсе (по руководству)
Задано (FIXED)Пользователь ввёл значение. Это «спецификация»; программа сама её не изменит.значение со звёздочкой *
Рассчитано (CALCULATED)Значение вычислил аппарат, которому принадлежит порт.значение со знаком =
Передано (PASSED)Значение пришло из соседнего порта по соединению.отображается как рассчитанное
Оценка (ESTIMATED)Начальное приближение для итераций по рециклу (оператор ~=).может перезаписываться при итерациях
Неизвестно (UNKNOWN)Данных пока недостаточно.значение None
Новое (NEW)Служебная отметка: значение получено в текущем проходе расчёта.не отображается

3.3. Степени свободы и контроль избыточности

У каждого аппарата есть физически обоснованное число параметров, которые нужно задать: например, у нагревателя — состав и расход на входе, T и P на входе, плюс одно из трёх: температура на выходе, тепловая нагрузка или перепад давления. Когда задано ровно столько, сколько нужно, аппарат считает остальное. Если задано меньше — часть значений останется None. Если больше — возможны противоречия. Руководство Sim42 называет это «Interactive Simulation and Degrees of Freedom Monitoring»[12].

Правило, на котором это построено, универсально: если в балансе не хватает ровно одного значения — оно вычисляется; если не хватает двух и более — программа ждёт новых данных; если известно всё — проверяется, что баланс сошёлся. Расчётное значение никогда не перезаписывает уже известное: оно только сверяется с ним. Расхождение сверх допуска считается противоречием (ConsistencyError). Сверка идёт не в абсолютных единицах, а в долях масштаба переменной, чтобы 0,1 К и 0,1 Па не считались равнозначными[12, 13].

Таблица 4. Масштабы переменных и допуск 0,0001 (по [12])
ПеременнаяМасштабЧто считается «одинаковым»
Температура100расхождение менее 0,01 К
Давление1000менее 0,1 кПа
Молярная энтальпия10 000менее 1 кДж/кмоль
Мольный расход1000менее 0,1 кмоль/ч
Массовый расход50 000менее 5 кг/ч
Мольная/массовая доля, доля пара1менее 0,0001

3.4. Порядок ввода не важен, история — тоже

Расчёт запускается не кнопкой «Рассчитать», а любым изменением данных. Команда hold приостанавливает расчёт — удобно, когда нужно ввести много данных; go возобновляет его. После go результат должен совпасть с вводом тех же данных без паузы. Самый сильный тест на корректность: «задал 80, потом изменил на 90» и «сразу задал 90» обязаны дать один и тот же результат[12]. Как солвер этого добивается, не храня граф зависимостей, разобрано в разделе 5.5.

3.5. Как читать результат

Важно

None в результатах — не обязательно ошибка. Чаще это признак недостатка исходных данных: волна расчёта остановилась, потому что ни одному аппарату не хватает информации. Ошибка — это сообщение о противоречии или о несходимости.

Таблица 5. Типичные сообщения солвера (по [12])
СообщениеЧто случилосьТипичная причина
MaxSolverIterExceededСхема не сошлась за максимальное число итерацийСлабая оценка рецикла, слишком жёсткий допуск
UnresolvedRecyclesПеречень портов рецикла, которые не сошлисьПоказывает, где «застряло»
ConsistencyErrorОстались противоречия между значениямиЛишние или несовместимые спецификации
ControllerConvergeFailКонтроллеры не достигли целиЦель вне диапазона [min; max], мал шаг
CouldNotConvergeВнутренний решатель аппарата не сошёлсяПлохое начальное приближение
CompNotNormalizedСумма долей состава не равна 1Ошибка ввода состава
лаборатория · песочница
открыть в полном окне

Полный набор: схема считается целиком.

Спецификации (10 задано)

°C
кПа
кмоль/ч
кПа
кДж/ч
°C
кПа
кПа
°C
кПа
кПа
кДж/ч
°C
кПа
кмоль/ч
Схема полностью рассчитана. Все величины определены.
S1 Feed
T25P100F100H0
Heater
ΔP нагревателя20Q нагревателя414150
S2
T80P80F100H4141.5
Pump
ΔP насоса500КПД η0.75Q идеальной работы900Q потерь (в тепло)300Q на валу1200
S3
T80.2P580F100H4153.5
Cooler
ΔP холодильника30Q холодильника302400
S4 Product
T40P550F100H1129.5

Синий — задано (FIXED), зелёный — рассчитано (CALCULATED), серый — не определено. Результат не зависит от того, в каком порядке вы вводите данные: попробуйте включать и выключать галочки.

Песочница использует модель из раздела 4. Попробуйте набор «Только с выхода»: по продукту и параметрам аппаратов солвер полностью восстановит вход; «Недоопределён» — получите None без ошибки; «Лишняя, конфликтная» — получите ConsistencyError.

4Разбор расчёта схемы Heater → Pump → Cooler

4.1. Постановка

Схема из трёх аппаратов: нагреватель (Heater), насос (Pump) и холодильник (Cooler). Вместо перепадов температур заданы температуры на выходе (Heater — 80 °C, Cooler — 40 °C), у насоса задан КПД. Рабочее тело — вода; расход и прочие условия приведены в таблице 6. Реализованные в лаборатории правила соответствуют описанию в [12] и [13]: флэш при двух известных из (T, P, H) и составе, мольный и энергетический балансы по принципу «ровно одно неизвестное», расчёт перепада давления, изоэнтропа насоса.

Таблица 6. Спецификации сценария и итоговый результат
СпецификацияЗначениеРассчитаноЗначение
Feed: T, P, состав25 °C, 100 кПа, zQ нагревателя414 150 кДж/ч
Heater: ΔP; T на выходе20 кПа; 80 °CP на выходе Heater80 кПа
Pump: ΔP; КПД η500 кПа; 0,75Работа на валу = Q_ид/η1200 кДж/ч
Cooler: ΔP; T на выходе30 кПа; 40 °CИдеальная работа Q_ид = F·v·ΔP900 кДж/ч (v = 0,018 м³/кмоль)
Product: расход F100 кмоль/чПотери в тепло Q_пот = Q_вал − Q_ид300 кДж/ч
T после насоса80,16 °C
P: S1→S2→S3→S4100 → 80 → 580 → 550 кПа
Q холодильника (отведено)302 400 кДж/ч

Упрощения: вода с cp = 75,3 кДж/(кмоль·К), H = cp·(T − 25); насос идеальный по изоэнтропе ΔH = v·ΔP. Единицы: T °C, P кПа, F кмоль/ч, H кДж/кмоль, Q кДж/ч.

Насос — составной аппарат: Ideal (идеальное сжатие по изоэнтропе), Waste (потери превращаются в тепло, ΔP = 0), Set (связывает КПД с энергиями: Q_ид = η·Q_вал) и Mix (энергобаланс Q_вал = Q_ид + Q_пот). Каждая часть решает только свою маленькую задачу; вместе они дают полный расчёт. Это пример вложенности аппаратов, о которой говорится в описании Sim42[1].

4.2. Что происходит при вводе

  1. T и P питания ничего не дают: для флэша нужен ещё состав. Аппарат — Feed — ставится в очередь, расчёт стоит.
  2. Состав питания завершает тройку (T, P, z): сработал флэш, появилась энтальпия H(S1). Heater получает «новое знание» и встаёт на вершину стека.
  3. ΔP нагревателя и T на выходе: давление на выходе Heater = 100 − 20 = 80 кПа; по (T, P) выхода — энтальпия H(S2). Расхода пока нет, поэтому тепловая нагрузка ещё неизвестна — в энергобалансе два неизвестных (F и Q).
  4. Насос и холодильник заполняют то, что можно вычислить без расхода: давления (580 и 550 кПа) и энтальпии потоков. Температура после насоса пока неизвестна: она зависит от потерь в тепло, а потери — от расхода.
  5. Последним вводится расход продукта F = 100 кмоль/ч — на выходе схемы. Мольный баланс отправляет его назад по всей схеме до питания; как только расход известен, в каждом энергобалансе остаётся ровно одно неизвестное, и один за другим определяются Q нагревателя, энергии насоса и нагрузка холодильника.

Именно пятый шаг показывает отличие от последовательного расчёта: расход, заданный в конце схемы, вычисляет всё в её начале. Модель можно пройти по шагам: фазы расчёта подсвечены, стек решения виден, значения вспыхивают в момент вычисления.

лаборатория · пошаговая модель
открыть в полном окне
Сценарий
Режим ввода
Порядок ввода данных
ΔP холодильника
Детализация
FIXED — заданоCALCULATEDUNKNOWNпередано (PASSED)стёрто (Forget)
1 · Ввод → в стек
2 · Запуск Solve()
3 · Pop с вершины
4 · Solve() аппарата
5 · UpdateConnection
6 · Стек пуст
⌫ Forget (забывание)

S1 Feedвход

T?°CP?кПаF?H?z?
▸

Heaterнагреватель

ΔP?кПаQ?
▸

S2выход Heater

T?°CP?кПаF?H?z?
▸
Pump — составной аппарат (Ideal + Waste + Set + Mix)

IdealS=const

ΔP?кПа
▸

Piпосле Ideal

T?°CP?кПаF?H?z?
▸

WasteΔP=0

Setη

η?
энергии (датчики)
Q ид?Q пот?Q вал?

MixE-баланс

▸

S3выход насоса

T?°CP?кПаF?H?z?
▸

Coolerхолодильник

ΔP?кПаQ?
▸

S4 Productпродукт

T?°CP?кПаF?H?z?

Схему можно прокрутить по горизонтали →

Стек решения _solveStack · LIFO

▼ вершина — решается следующей
стек пуст
дно стека (решится последним)
стек забывания
пуст
Исходное состояние
Схема построена, ничего не известно. Стеки пусты. Дальше — ввод спецификаций в заданном порядке.
После каждой команды ввода сразу запускается расчёт (root.Solve()).

Результаты расчёта

Q нагревателя, кДж/ч?
Работа на валу насоса, кДж/ч?
из них идеальная?
потери в тепло?
T после насоса, °C?
Q холодильника, кДж/ч?
Расход F, кмоль/ч?
P: S1→S2→S3→S4, кПа? → ? → ? → ?

Неопределённых величин на этом шаге: 29

шаг 1 / 73

Упрощения модели: вода (cp = 75,3 кДж/кмоль·К, H = cp·(T−25)), насос идеальный по изоэнтропе ΔH = v·ΔP; поток показан одной карточкой (в коде у потока отдельные порты In/Out); датчики энергии сведены в «провода». Единицы: T °C, P кПа, F кмоль/ч, H кДж/кмоль, Q кДж/ч. Клавиши: ← → шаг, пробел — пуск/пауза.

4.3. Порядок ввода меняет путь, но не итог

Если вводить те же данные от выхода к входу, число крупных шагов (один шаг — один решённый аппарат или ввод) меняется, а итоговые значения остаются теми же. В режиме hold → go очередь копится и решается одним проходом, поэтому шагов заметно меньше.

Таблица 7. Число крупных шагов расчёта сценария (модель лаборатории)
Порядок вводаРасчёт после каждого вводаhold → go
от входа к выходу7333
от выхода к входу7036

Итоговые значения во всех четырёх вариантах совпадают; это проверяется автоматическим тестом.

4.4. Смена спецификации: забывание и решение

Что произойдёт, если после полного расчёта изменить температуру выхода Heater с 80 до 90 °C? Все значения, полученные из 80 °C, устарели — но солвер не хранит карту зависимостей. Сначала выполняется фаза «забывания»: новая спецификация на время притворяется неизвестной, старые рассчитанные значения скрыты, аппараты решаются «вхолостую» и подтверждают только то, что от изменённой величины не зависит; остальное стирается. В лаборатории (сценарий «+ смена T выхода 80→90») при этом стирается 23 величины в шести проходах; затем новая спецификация записывается, и обычный расчёт заново заполняет схему. Результат — тепловая нагрузка 489 450 кДж/ч — в точности совпадает с расчётом «с чистого листа» при 90 °C. Подробнее — в разделе 5.5.

4.5. Рецикл: оценка на потоке и итерации

Если выход схемы возвращается на вход, возникает замкнутый круг: чтобы посчитать смеситель, нужно знать рецикл, а чтобы знать рецикл — посчитать смеситель. Волна распространения упирается в неизвестное. В Sim42 специального аппарата «Recycle» нет: пользователь ставит оценку (оператор ~=) в любой порт — он становится точкой разрыва[1]. Оценочные значения не передаются дальше как факт; направление вычислений определяется тем, во входном или в выходном порту поставлена оценка (рисунок 4).

Оценка в ВХОДНОМ порту — расчёт идёт вперёдоценканаправление вычислений →Оценка в ВЫХОДНОМ порту — расчёт идёт назадоценка← направление вычислений
Рисунок 4. Направление расчёта при оценке во входном и в выходном порту (схема автора по описанию в [1, §3.7]).

Далее схема считается целиком, результат сравнивается с оценкой, ошибка нормируется масштабом переменной, и оценка обновляется: первый шаг — простая подстановка, затем метод Бройдена (квази-ньютоновский, без расчёта производных)[12, 13]. Автоматического выбора разрывного потока в коде не обнаружено — это решает пользователь[12].

лаборатория · рецикл и метод Бройдена
открыть в полном окне

Оценка на потоке рецикла

Параметры солвера

Масштабы переменных: R — 1000, T — 100. Допуск (MaxError) 0.0001. Точное решение схемы: R ≈ 255.5 кмоль/ч, T ≈ 298.6 °C. Решение одно, независимо от начальной оценки.

Простая подстановка: не сошлосьБройден: 9 ит.
1e01e-21e-41e-61e-8допуск 0.000112468101214161820итерация
Бройден: Сошлось за 9 итераций (ошибка 8.3e-7 < 0.0001).
Итер.R оценкаT оценкаR получено g(x)T получено g(x)ОшибкаШаг (доли масштаба)
110.00300.0078.84299.736.88e-20.0500; -0.0020шаг ограничен
260.00299.80114.71299.655.47e-20.0500; -0.0012шаг ограничен
3110.00299.68150.59299.544.06e-20.0500; -0.0015шаг ограничен
4160.00299.53186.51299.402.65e-20.0500; -0.0020шаг ограничен
5210.00299.32222.48299.221.25e-20.0446; -0.0028
6254.56299.05254.61298.968.54e-40.0003; -0.0009
7254.83298.96254.85298.896.87e-40.0006; -0.0030
8255.42298.66255.42298.651.04e-40.0001; -0.0005
9255.52298.60255.52298.608.27e-70.0000; 0.0000

Схема считается целиком при заданной оценке потока рецикла; результат g(x) сравнивается с оценкой x, ошибка нормируется шкалой переменной. Первый шаг — подстановка (единичный якобиан), дальше метод Бройдена обновляет обратный якобиан по предыдущим шагам. Шаг ограничен параметром MaxRecycleStep (по умолчанию 0,05 шкалы) — при срабатывании ограничения строка таблицы помечена.

Модель рецикла — учебная (смеситель, реактор, сепаратор, линия возврата), а не схема из Sim42. Параметры солвера соответствуют значениям по умолчанию: MaxNumIterations = 20, MaxRecycleStep = 0,05, MaxError = 0,0001[12, 13]. При них простая подстановка не укладывается в 20 итераций и завершается сообщением MaxSolverIterExceeded, а метод Бройдена сходится за 9.

Что именно проверено автоматически

Для обеих моделей написаны тесты: итог не зависит от порядка ввода и от режима hold/go; «80 → 90» даёт то же, что «сразу 90»; недоопределённость даёт None, но не ошибку; совместимая лишняя спецификация проходит, несовместимая вызывает ConsistencyError; подстановка и Бройден приходят к одному решению, не зависящему от начальной оценки. Раздел 7 объясняет, почему именно такие свойства стоит проверять.

5Устройство ядра: структуры данных и алгоритмы

Раздел адресован разработчикам. Описание построено по аналитическому разбору исходного кода Sim42 v2.0.0.0 (каталоги sim/solver, sim/unitop; репозиторий[7]) и тексту диссертации[1]; анализ статический, программа не запускалась[13]. Имена классов и методов (BasicProperty, Flowsheet.InnerSolve и т. п.) даны по коду; номера строк в источнике могут смещаться. В режиме «Специалист» подразделы свёрнуты; режим «Разработчик» раскрывает их.

для разработчика5.1. Архитектура слоёв▸

Ядро — событийный решатель «распространения ограничений» (constraint propagation) над графом аппаратов и портов[13]. Порядок расчёта нигде не предопределён: он задаётся динамически стеком аппаратов, «получивших новую информацию». В README проекта заявлено, что ядро спроектировано независимым и от интерфейсов пользователя, и от поставщиков термодинамики; в коде это подтверждается: ядро общается с внешним миром через InfoMessage (callback сообщений), ThermoAdmin (термодинамика) и S42Glob.unitSystem (единицы)[5, 13].

Клиенты ядракомандная строка (CLI) · графический клиент · веб-интерфейс (SimBA) · коммерческий интерфейсSetValue · Connect · hold · goFlowsheet — оркестратор (тоже аппарат)стеки: solve · forget · resetNewCalc · resetNewFixed · iteration · consistencyErrorSolverForget() → InnerSolve() → ControllerSolver.Solve()op.Solve() · port.UpdateConnection()Аппараты (unit operations)Mixer · Heater · Flash · Tower · Pump · контроллерыформулы (Set, Dyadic) · потоки — тоже аппаратыПеременные и портызначение + статус + тип (масштаб, min, max)порты материальные · энергетические · сигнальныеFlash(…) · единицыТермодинамика (ThermoAdmin → внешний провайдер) · система единиц
Рисунок 5. Слои архитектуры симулятора (схема автора по [1], [13]).

Схема (Flowsheet) — тоже аппарат, поэтому вложенные «flowsheet» работают по тем же правилам; поток — тоже аппарат с портами In и Out. Это позволяет собирать сложные аппараты из простых: теплообменник — из нагревателя и охладителя[1].

для разработчика5.2. Модель данных: переменная, статус, тип▸

Каждое значение — объект BasicProperty с полями value, calcStatus и ссылкой на PropertyType (имя, флаги флэша, тип единиц, scaleFactor, minValue, maxValue). Экземпляры типов лежат в глобальном словаре и разделяются всеми переменными данного типа[13].

Portпорт аппаратаBasicPropertyодна переменная (T, P, F…)valueчисло или NonecalcStatusкто и как получил значениеtypeобщий справочник типаPropertyType (общий для всех переменных типа)scaleFactorмасштаб допусковminValue / maxValueграницыunitTypeтип единицcalcTypeфлаги флэшаПравило «первый записавший побеждает»Расчётное значение не перезаписываетизвестное: оно только проверяетсяна согласованность (в пределах допуска).Перезаписать может только пользователь(FIXED) или фаза «забывания».Допуск: |значение − новое| / scaleFactor < MaxError
Рисунок 6. Устройство переменной и порта (схема автора; идея по [1, рис. 19], перерисована).
Таблица 8. Статус значения — битовая маска
КонстантаБитНазначение
UNKNOWN_V1значения нет (value is None)
FIXED_V2задано пользователем (спецификация)
CALCULATED_V4вычислено аппаратом-владельцем порта
PASSED_V8передано через соединение от соседнего порта
NEW_V16установлено в текущем проходе (сбрасывается стеками ResetNew*)
ESTIMATED_V32оценка для итерации рецикла (комбинируется с FIXED)
PARENT_V64вычислено родительским аппаратом в порт дочернего
UNKNOWNзначения нет (None)FIXEDзадано пользователемCALCULATEDвычислил владелец портаPASSEDот соседа по связиFIXED + ESTIMATEDоценка для рецикла (~=)ввод (=)правило аппаратаUpdateConnectionоператор ~=«забывание»: стереть устаревшееFIXED и CALCULATED не перезаписываются расчётом — только сверяются на согласованность
Рисунок 7. Статусы значения и переходы между ними (схема автора по [13]).

Логика записи BasicProperty.SetValue(value, calcStatus):

Таблица 9. Ветки SetValue (по [13])
ВеткаПоведение
Без портапросто присвоить (вспомогательные переменные)
FIXED_Vприведение к float; None → UNKNOWN; режим порта (FIXALL/ESTIMATEALL); если статус и значение не изменились — выход; пометка NEW_V; вызов port.PropertyModified; нулевой расход заменяется на 1e-40
UNKNOWN_Vстирание: допустимо только value=None; ставит UNKNOWN|NEW, вызывает PropertyModified
CALCULATED_V / PASSED_VNone игнорируется. Если значение уже есть (и нет «забывания») — CheckTolerance: старое сохраняется, при расхождении выше допуска — PushConsistencyError. Если значения нет — запись с NEW|status и PropertyModified
Правило «первый записавший побеждает»

Расчётные значения никогда не перезаписывают известное: они только проверяются на согласованность. Перезаписать можно лишь спецификацией (FIXED) или после «забывания». Это основа детерминизма и защиты от переопределения[13].

Метод GetValue() зависит от режима солвера: вне «забывания» возвращает value; в режиме «забывания» (op.IsForgetting()) новые FIXED-значения выглядят как None, а старые CALCULATED скрыты, если не помечены NEW[13]. Эта подмена видимости превращает обычный op.Solve() в «холостой» прогон (см. 5.5).

для разработчика5.3. Порты и распространение: PropertyModified и UpdateConnection▸

Порты бывают материальные (Port_Material: состав, массивные свойства, кэш флэша), энергетические и сигнальные; типы — битовые флаги IN, OUT, MAT, ENE, SIG. Порт соединяется только с одним портом совместимого типа, вход — только с выходом; ConnectTo разрывает старое соединение, оповещает аппарат и сразу вызывает UpdateConnection()[13].

Точка, где изменение значения превращается в события солвера, — Port.PropertyModified(property, calcStatus):

Таблица 10. Реакция порта на изменение значения (по [13])
Новый статусДействие
FIXED_VPushSolveOp(владелец) + PushResetFixedPort(порт) + PushForgetOp(владелец)
UNKNOWN_V | PARENT_VPushForgetOp и PushSolveOp родителя владельца
PASSED_V или PARENT_VPushSolveOp(владелец) + PushResetCalcPort(порт): сосед получил знание → его надо считать
CALCULATED_VPushResetCalcPort(порт): только сброс отметки NEW
UNKNOWN_VPushForgetOp(владелец) + PushSolveOp(владелец)

Port.UpdateConnection() вызывается солвером после op.Solve() для всех портов аппарата:

  1. Нет соединения — стираются свои значения со статусом PASSED.
  2. Порт «оценочный» (_estimated) ничего не передаёт: у соседа стираются PASSED-значения — так «разрывается» рецикл.
  3. Если у соседа значение ESTIMATED, а у себя значение известно — новое значение попадает в стек итераций (PushIterationProperty).
  4. Обычный режим: если у себя значение известно и оно не PASSED — connProp.SetValue(myValue, PASSED_V); дальше сработает PropertyModified(PASSED_V) соседа, и он встанет в очередь.

В режиме «забывания» переданное значение соседа стирается, если у источника оно неизвестно, «новое-несчитанное», «старое-считанное» либо не совпадает[13].

для разработчика5.4. Шесть стеков и главный цикл Flowsheet▸

Flowsheet владеет шестью списками-стеками; аппарат любого уровня вложенности помещается в стек корневого солвера (методы Push* у не-Flowsheet аппарата делегируют вызов родителю). Флаги _stackStatus (ON_SOLVE_STACK и др.) исключают двойное помещение; BlockPush(1) блокирует помещение аппарата в стеки, пока он решается — иначе его собственные записи в порты ставили бы его в очередь заново и цикл стал бы бесконечным[13].

Таблица 11. Стеки Flowsheet (по [13], §7.1)
СтекЭлементыЗаполняетсяПотребляется
_solveStackаппаратыPropertyModified (FIXED/PASSED/UNKNOWN), смена параметровInnerSolve: op.Solve()
_forgetStackаппаратыPropertyModified (FIXED/UNKNOWN), SetStateSolverForget: холостой Solve + Forget
_resetNewCalcStackпортыPropertyModified (CALCULATED/PASSED)после прохода: снять NEW_V с расчётных значений
_resetNewFixedStackпортыPropertyModified (FIXED)после forget-прохода: снять NEW_V со спецификаций
_iterationStackсвойства ESTIMATED с новым значениемUpdateConnection (оценочная сторона)цикл рециклов (Бройден)
_consistencyErrorStackсвойства с конфликтомCheckToleranceблокируют сходимость; в конце — ConsistencyError
Ввод значенияSetValue(FIXED)PropertyModifiedсобытия → очередиSolverForget()стек забыванияInnerSolve()стек решенияInnerSolve: пока стек решения не пустpop вершиныLIFO-стекop.Solve()правила аппаратаUpdateConnection()передать значения соседямсоседPushSolveOpсосед получил новое знание → встал на вершину стека → «волна» расчёта идёт по схемеСтек пуст → есть оценки рецикла? да: проверка ошибки и шаг Бройдена, затем повтор · нет: готовоПосле InnerSolve — внешний цикл контроллеров (ControllerSolver)
Рисунок 8. Цикл решения: ввод → события → забывание → InnerSolve (схема автора по [13]).
Листинг 1. InnerSolve (упрощённо, по [13])
InnerSolve():
    consistencyErrorStack.clear()
    iter = 0; jacobian = None
    while iter < maxIter:
        iter += 1
        SolverForget()
        if hold: return
        while op = PopSolveOp():                  # LIFO
            if op is not self and op.Ignored is None:
                op.BlockPush(1)
                try:
                    op.Solve()
                    for port in op.GetPorts(): port.UpdateConnection()
                finally:
                    op.BlockPush(0)
        ResetNewCalc()
        n = len(iterationStack)
        if n == 0: break                          # рециклов нет — готово
        maxError = max(prop.CalculateError(newValue))   # итерации + consistency
        if maxError < tol and no consistencyErrors: break
        if iter + 1 < maxIter: BroydenStep()      # см. 5.6
    if iter >= maxIter: raise SimError("MaxSolverIterExceeded")
    if consistencyErrorStack: raise ConsistencyError(...)

После InnerSolve вызывается ControllerSolver.Solve() (не в режиме «забывания»). Командный интерфейс после каждой команды вызывает root.SolverForget(), в конце цепочки команд — root.Solve(); hold ставит флаг, go снимает его и запускает расчёт[13].

для разработчика5.5. «Забывание → решение»: инвалидация без графа зависимостей▸

Самое нетривиальное решение ядра. При изменении или удалении спецификации устаревшие рассчитанные значения нужно стереть, но граф зависимостей «кто от кого получен» не хранится. Вместо него — двухпроходная схема[13]:

Листинг 2. SolverForget (упрощённо, по [13])
SolverForget():
    _isForgetting = 1
    while op = PopForgetOp():                     # LIFO
        if op is not self:
            op.BlockPush(1)
            try:
                if op.Ignored is None: op.Solve()  # "холостой" прогон
                for port in op.GetPorts():
                    if port.GetParentOp() == op: port.UpdateConnection()
                op.Forget()                        # стереть CALCULATED без NEW
                while port = PopResetCalcPort(): port.ResetNewCalc()
            finally:
                op.BlockPush(0)
    while port = PopResetFixedPort(): port.ResetNewFixed()
    _isForgetting = 0

Пока _isForgetting = 1, GetValue() скрывает старые расчётные значения и новые спецификации (5.2). Аппарат, решаясь «вхолостую», выводит только то, что не зависит от изменённой информации, — всё выведенное получает NEW_V. Затем op.Forget() стирает все CALCULATED без NEW_V, то есть именно устаревшее, а стёртое значение через PropertyModified(UNKNOWN) будит соседей. Граф зависимостей нигде не хранится — он «открывается» самими аппаратами. В аппаратах на системах уравнений (теплообменники, реакторы) в фазе «забывания» тяжёлый итерационный расчёт не выполняется, они лишь подтверждают известное — ради скорости интерактивной работы[13].

Как это показано в лаборатории

В модели лаборатории та же идея реализована на упрощённой структуре (переменная в потоке — одна ячейка, а не пара портов In/Out): видимость значений в фазе «забывания» подменяется так же, а стирание «с запасом» дальше распространяется по соседям. Свойство, ради которого всё затеяно, проверяется тестом: изменить значение — то же самое, что сразу задать новое. Остаётся предостережение из исходного разбора: исключения в фазе «забывания» в коде проглатываются (except: pass), что скрывает дефекты[13].

для разработчика5.6. Рециклы и метод Бройдена▸

Пользователь разрывает цикл, задавая оценку в порту (~=, статус FIXED|ESTIMATED). Решается F(x)=x−g(x)=0F(x)=x-g(x)=0F(x)=x−g(x)=0 (в коде «ошибка» — это оценка минус полученное значение), где g(x)g(x)g(x) — значения, вычисленные схемой при оценках xxx. Переменные нормируются: xi=valuei/scaleFactorix_i=\mathrm{value}_i/\mathrm{scaleFactor}_ixi​=valuei​/scaleFactori​. Первый шаг — простая подстановка (обратный якобиан — единичная матрица, xk+1=g(xk)x_{k+1}=g(x_k)xk+1​=g(xk​)); далее — «хороший» метод Бройдена, где BkB_kBk​ — приближение обратного якобиана FFF[13, 14]:

Δxk=−BkF(xk),Bk+1=Bk+(δx−Bk δf) δx⊤Bkδx⊤Bk δf\Delta x_k=-B_k F(x_k),\qquad B_{k+1}=B_k+\frac{(\delta x-B_k\,\delta f)\,\delta x^{\top}B_k}{\delta x^{\top}B_k\,\delta f}Δxk​=−Bk​F(xk​),Bk+1​=Bk​+δx⊤Bk​δf(δx−Bk​δf)δx⊤Bk​​

где δx=xk−xk−1\delta x=x_{k}-x_{k-1}δx=xk​−xk−1​, δf=f(xk)−f(xk−1)\delta f=f(x_k)-f(x_{k-1})δf=f(xk​)−f(xk−1​). Если ∣δx⊤Bk δf∣<10−100|\delta x^{\top}B_k\,\delta f|<10^{-100}∣δx⊤Bk​δf∣<10−100, матрица не меняется. Шаг ограничивается: если max⁡i∣Δxi∣>MaxRecycleStep\max_i|\Delta x_i|>\mathrm{MaxRecycleStep}maxi​∣Δxi​∣>MaxRecycleStep (по умолчанию 0,05 шкалы), вектор масштабируется целиком до этого предела; доли ограничиваются диапазоном [0,1][0,1][0,1]. Новые оценки записываются как FIXED|ESTIMATED[13].

Таблица 12. Параметры Flowsheet (по умолчанию, по [12], [13])
ПараметрЗначениеСмысл
MaxError0,0001допуск сходимости рециклов и проверки согласованности (в долях масштаба)
MaxNumIterations20максимум итераций по рециклам
MaxControllerIter20максимум итераций решателя контроллеров
MaxRecycleStep0,05ограничение размера шага (в долях масштаба)
RecycleDetailsвыкл.подробный вывод ошибок по каждой переменной рецикла

Нюанс реализации: ошибка считается как ∣old−new∣/scaleFactor|\mathrm{old}-\mathrm{new}|/\mathrm{scaleFactor}∣old−new∣/scaleFactor; если у типа переменной scaleFactor равен None, ошибка = 0, а при нормировке возможно деление на None — для пользовательских типов, участвующих в рециклах, масштаб обязателен[13]. Автоматического выбора разрывных потоков в коде нет.

для разработчика5.7. Контроллеры: внешний слой над InnerSolve▸

Controller — аппарат без собственной физики с сигнальными портами In (наблюдаемая), Out (управляемая), Target, StepSize, Minimum, Maximum. Его Solve() только регистрирует контроллер в ControllerSolver корневой схемы[13]. Решатель контроллеров работает снаружи основного расчёта: строит обратный якобиан численным дифференцированием (возмущение 0,1 шага; на каждое возмущение выполняется InnerSolve), затем итерирует до MaxControllerIter: шаг Δ=−Je\Delta=-J eΔ=−Je с ограничением по StepSize и границам, пересчёт схемы, деление шага на 4 при неубывании ошибки, обновление якобиана по Бройдену. При неудаче — ControllerConvergeFail. Вариант «деление пополам» реализован, но принудительно отключён (выбор сведён к Бройдену), — кандидат на удаление или доработку[13].

для разработчика5.8. Аппараты с внутренним решателем: уравнения и колонна▸

Большинство аппаратов — простые правила баланса, решаемые за один проход. Но теплообменники, реакторы (CSTR, PFR), трубы и эжекторы наследуют EquationBasedOp: шаблон Solve() — флэш портов; сбор вектора неизвестных; проверка достаточности данных; в режиме «забывания» — выход; отключение лишних спецификаций; решение нелинейной системы (Ньютон–Рафсон, Бройден или метод секущих; линейный поиск с уменьшением шага в 4 раза; запуск с последнего сошедшегося решения). Для ОДУ (PFR) — интеграторы Рунге–Кутты 4-го порядка и Эйлера[13].

Колонна (Tower) реализует алгоритм inside/out Рассела: внешний цикл обновляет упрощённые модели K-значений и энтальпий, внутренний решает систему по ступеням (неизвестные — логарифмы факторов отпарки и отборов; якобиан обновляется по Бройдену). Решение колонны встроено в общий протокол: результаты кладутся в порты со статусом CALCULATED, поэтому остальной солвер видит колонну как обычный аппарат и спецификации могут распространяться по схеме вверх и вниз от неё[1, 13].

для разработчика5.9. Шаблон простого аппарата: Heater, Balance, формулы▸
Листинг 3. HeaterCooler.Solve (по [13])
def Solve(self):
    self.FlashAllPorts()                 # что можно — сфлэшить
    self.balance.DoBalance()             # мольный + энергетический баланс
    hadPressure = self.CalcDP()          # перепад давления
    didBalance = 0
    while self.FlashAllPorts():          # новый флэш дал новое знание?
        self.balance.DoBalance()         # → снова баланс
        didBalance = 1
    if not didBalance: self.balance.DoBalance()
    if not hadPressure: self.CalcDP()

Паттерн «флэш ↔ баланс до неподвижной точки» повторяется почти во всех аппаратах: аппарат не знает, какие значения известны, — он просто пробует применить правила, а неизвестное остаётся None. Balance объединяет входные и выходные порты и выполняет материальный, мольный (покомпонентно, линейной системой) и энергетический балансы по правилу «ровно одно неизвестное»; при полной информации проверяет согласованность через CheckTolerance. Формулы (Dyadic, Set: v1=a v0+bv_1=a\,v_0+bv1​=av0​+b) двунаправлены: при известных результате и одном входе вычисляется второй вход[13].

6Сравнение подходов и ограничения

6.1. Что отличает непоследовательно-модульный солвер

Таблица 13. Сравнение подходов к решению схемы (обобщение автора по [1], [12], [13])
СвойствоПоследовательно-модульныйУравнение-ориентированныйНепоследовательно-модульный (Sim42)
Порядок расчётазадан схемой: от сырья к продуктуне нужен: единая система уравненийдинамический: стек аппаратов, получивших новую информацию
Где задавать данныена входах; для выходов нужны алгоритмы оценокгде угодно при выполнении степеней свободыгде угодно; нехватка — None, избыток — проверка
Сбой решениярезультаты решённых аппаратов остаются доступнымирешателя нет — решения нет вовсечастичный результат: считается всё, что можно
Рециклитерации по всей схемечасть общего решенияитерации по всей схеме, только где без них нельзя
Отладкапо аппаратамтруднавидна «волна» расчёта: что, откуда и почему не вычислено
Динамика—естественна, быстро после первого решениядорога (итерации по всей схеме)
Избыточные данные——ConsistencyError: расхождение сверх допуска
Смена спецификации——забывание устаревшего + решение, без графа зависимостей

Столбцы «последовательный» и «уравнение-ориентированный» опираются на перечень достоинств и недостатков в [1, §1.1.1]; прочерк — в источниках не рассматривалось. Последний столбец — по [12], [13]. Внутренний алгоритм HYSYS закрыт, поэтому перенос выводов о Sim42 на HYSYS остаётся гипотезой.

6.2. Ограничения и технический долг Sim42

По результатам статического анализа кода[13] отмечены наблюдения, существенные и для самого алгоритма, и для возможного переноса.

Таблица 14. Наблюдения о ядре Sim42 (по [13], §14.1)
НаблюдениеВлияние
Python 2 (print-оператор, has_key, cPickle, rexec и т. д.)Нужна миграция на Python 3; rexec отсутствует — заменить механизм версионирования сохранённых случаев
Остатки обращений к Numeric без импорта в EquationSolver, Controller, TowerNameError в чистой среде; следы незавершённой миграции на NumPy [7]
Термодинамический провайдер — бинарная библиотека (vmg.pyd, Windows)Привязка к платформе и лицензии; нужен адаптер к другому провайдеру через ThermoAdmin
Глобальные PropertyType (масштабы, границы общие)Нельзя иметь две симуляции с разными допусками в одном процессе
Побочные эффекты в стеках и флагах (_isForgetting, BlockPush)Затрудняют параллелизм и тестирование; потокобезопасности нет
Порядок обхода dict.values() в Python 2 влияет на порядок портовПри миграции порядок станет «порядком вставки»: возможны различия в последовательности расчёта — нужна регрессия
Разрыв рециклов только пользователем (~=)Нет автоматического выбора разрывных потоков (анализа контуров)
Исключения в фазе «забывания» проглатываютсяСкрываются дефекты; добавить отладочное логирование
Монолитные модули (Tower — около 6600 строк, Heater — около 6100)Разбиение по ответственности перед сопровождением
Критика подхода в целом

Непоследовательно-модульный подход наследует недостатки модульного: рецикл сходится итерациями по всей схеме, динамика дорога[1]. Кроме того, поведение зависит от того, какие аппараты и в каком порядке «получили знание»; поэтому гарантия детерминизма — свойство, которое необходимо проверять тестами, а не считать данностью (раздел 7).

6.3. Что стоит сохранить в любом наследнике

  • Идея статусов значений и принцип «расчёт не перезаписывает известное, а проверяет» — простая модель защиты от переопределения.
  • Инвалидация без графа зависимостей: экономит память и код, хорошо работает при интерактивной работе.
  • Нормировка ошибок шкалами типов — единый механизм допусков.
  • Слабая связность ядра с термодинамикой, интерфейсами и единицами.
  • Протокол Solve(), позволяющий подключать аппараты любой сложности — от формулы до колонны[13].

7Практические следствия

7.1. Для специалиста по моделированию

  1. None — не ошибка. Это сигнал о недоопределённости: сосчитайте, каких спецификаций не хватает (например, давление на выходе холодильника). Ошибка — сообщение о противоречии или несходимости.
  2. Противоречие — это лишняя спецификация. Если солвер сообщил о конфликте по порту, ищите значение, которое задано дважды разными путями (например, P на выходе и ΔP одновременно).
  3. Порядок ввода не должен менять результат. Если меняет — подозревайте оценки рецикла, ограничение числа итераций или дефект, а не «особенность» программы. Сравнивайте «изменил A→B» с «сразу B».
  4. Для рецикла задавайте оценку близко к ожидаемой и проверяйте масштаб переменных: слишком жёсткий допуск или далёкая оценка приводят к MaxSolverIterExceeded. Перечень несошедшихся портов показывает, где искать.
  5. Отличайте ошибки термодинамики от ошибок солвера. Если одна и та же термодинамическая задача решается стабильно при ручном вводе, но «плывёт» в схеме с рециклом — проблема в солвере; если не решается вовсе — в термо-пакете[12].
  6. Нулевой расход — «почти ноль». Внутри он заменяется на 10−40 ради избежания деления на ноль, это нужно учитывать при проверке нулевых потоков[12, 13].

7.2. Для разработчика: свойства, которые стоит проверять

Для солвера с динамическим порядком расчёта главная гарантия — детерминизм результата. Поэтому тестировать нужны метаморфические свойства, а не только значения[13]:

  • независимость от порядка ввода и от режима hold/go;
  • «задал A → изменил на B» равно «сразу B» (проверка фазы «забывания»);
  • идемпотентность Solve(): повторный вызов не меняет состояния;
  • недоопределённость даёт None без ошибки, избыточная несовместимая спецификация — ConsistencyError;
  • сходимость рецикла к одному решению при разных начальных оценках;
  • модульные тесты ветвей SetValue/GetValue, UpdateConnection (оценочный порт, режим «забывания»), UpdateJacobian.

В репозитории Sim42 уже есть регрессионные сценарии (.tst — текстовые команды CLI: рециклы, колонны, реакторы, компрессоры); при миграции их следует прогнать как «золотой» набор — сначала на оригинале, затем на переносе — сравнивая результаты с допуском шкал[13]. Именно эти свойства (кроме последнего) проверяют тесты лаборатории: набор из 17 автоматических тестов к двум моделям запускается встроенным тест-раннером Node.js.

8Заключение

Принцип непоследовательно-модульного солвера укладывается в несколько идей: значение хранится вместе со статусом происхождения; расчёт распространяет частичную информацию в обе стороны и никогда не перезаписывает известное, а лишь сверяет; порядок вычислений определяет стек аппаратов, получивших новое знание; устаревшее находится не обходом графа, а «холостым» прогоном; итерации нужны только там, где информация упирается в замкнутый контур, — и тогда применяется подстановка и метод Бройдена. Эти идеи делают поведение программы предсказуемым для специалиста: результат определяется набором спецификаций, а не историей ввода.

История проекта показывает, что открытый Sim42 создан в среде калгарийской школы моделирования и при участии автора симулятора Hysim; это объясняет, почему описанный принцип совпадает по классу с принципом HYSYS. Одновременно важно не выдавать гипотезу за факт: прямой связи кода Sim42 и HYSYS открытые источники не подтверждают. Интерактивная лаборатория, сопровождающая статью, — самостоятельная реализация идей, проверенная автоматическими тестами; она позволяет увидеть механизм на конкретных числах и проверить свойства, которые обычно принимают на веру.

9Методика и ограничения исследования

  • Анализ кода — статический. Исходный код Sim42 (Python 2, бинарные зависимости) не запускался; описание поведения построено по тексту кода и встроенному руководству[12, 13]. Подробно разобраны Variables.py, Ports.py (ключевые методы), Flowsheet.py, EquationSolver.py, Controller.py, UnitOperations.py, часть Balance.py, Mixer.py, Stream.py, Equation.py, Heater.py (HeaterCooler) и структура Tower.Solve. Не разбирались построчно: тела DoMoleBalance/DoVolBalance, SolveFlowMatrix, Heater.HeatExchanger, KineticReactor, PipeSegment, GUI и веб-интерфейс.
  • Лаборатория — упрощённая модель. Рабочее тело — вода; потоки показаны одной карточкой; рецикл — учебная схема, а не пример из Sim42. Корректность проверена тестами на свойства алгоритма, но не сверкой с оригинальным кодом (он не запускался).
  • История — по открытым источникам. Первоисточники — страницы sim42.org из веб-архива (2002–2004) и диссертация; профили участников — их самоописания. Не найдены: оригинал работы Svrcek et al. (1984); независимое подтверждение роли Morris в Hyprotech; судьба проекта после 2004 года (репозиторий создан энтузиастом в 2011 году, активное сопровождение не обнаружено).
  • Лицензионные замечания носят справочный характер и не являются юридическим заключением. Рисунки из диссертации не воспроизводятся (на титульном листе указан запрет воспроизведения), схемы выполнены заново; два снимка веб-архива приведены уменьшенными как историческая иллюстрация со ссылкой на источник.
  • Связь с HYSYS — гипотеза на уровне людей и идей (раздел 2.3), а не утверждение о коде.
Атрибуция

This article discusses software developed by the SIM42 Software Foundation (http://www.SIM42.org/). Sim42® and the names of its authors belong to their respective owners; the interactive lab is an independent implementation and is not affiliated with the SIM42 Software Foundation.

Список источников

  1. [1]Cota Elizondo R. C. Development of an Open Source Chemical Process Simulator: M.Sc. thesis. Calgary: University of Calgary, Department of Chemical and Petroleum Engineering, 2003. DOI 10.11575/PRISM/15852. https://ucalgary.scholaris.ca/handle/1880/42288 первоисточник
  2. [2]The History of Sim42 [Электронный ресурс] // sim42.org. Архивная копия Internet Archive от 09.12.2002. https://web.archive.org/web/20021209085237/http://sim42.org/cast_profiles/history.html первоисточник
  3. [3]Cast Profiles: Craig Morris [Электронный ресурс] // sim42.org. Архивная копия Internet Archive от 09.12.2002. https://web.archive.org/web/20021209020008/http://sim42.org/cast_profiles/craig.html первоисточник
  4. [4]Cast Profiles: Marco Satyro [Электронный ресурс] // sim42.org. Архивная копия Internet Archive от 09.12.2002. https://web.archive.org/web/20021209015736/http://sim42.org/cast_profiles/marco.html первоисточник
  5. [5]Simulator42 [Электронный ресурс] // sim42.org. Главная страница. Архивная копия Internet Archive от 08.12.2002. https://web.archive.org/web/20021208125037/http://www.sim42.org/ первоисточник
  6. [6]The SIM42 Software License, Version 1.0. © 2002 SIM42 Software Foundation. Цит. по: [1], Приложение 1 (дословная копия). первоисточник
  7. [7]jonathanxavier/sim42 [Электронный ресурс]: репозиторий, импорт Sim42 v2.0.0.0 с SourceForge (OllinTS), 24.07.2011; конвертация Numeric → NumPy, 2011. GitHub. https://github.com/jonathanxavier/sim42 код
  8. [8]OllinTS — thermodynamic server for chemical simulation [Электронный ресурс] // SourceForge. Проект зарегистрирован 02.10.2007, последнее обновление 28.11.2015, лицензия GPLv2. https://sourceforge.net/projects/ollints/ вторичный
  9. [9]Aspen HYSYS [Электронный ресурс] // Wikipedia, the free encyclopedia. https://en.wikipedia.org/wiki/Aspen_HYSYS вторичный
  10. [10]About the Authors: W. Y. Svrcek [Электронный ресурс] // A Real-Time Approach to Process Control. University of Calgary, Department of Chemical and Petroleum Engineering. https://www.ucalgary.ca/ENCH/realtime/authors-svrcek.htm вторичный
  11. [11]Why not contribute to Ascend? Sim42 license issue [Электронный ресурс] // группа opsim, Google Groups, 18.03.2008. https://groups.google.com/g/opsim/c/v-aJMVa79bE вторичный
  12. [12]Петрусенко В. С. Как работает солвер Sim42: руководство для бизнес-аналитика (по исходному коду sim/solver, sim/unitop). 16.10.2026. материалы автора
  13. [13]Петрусенко В. С. Солвер Sim42: техническое описание реализации (структуры данных, алгоритмы, потоки управления, риски). 16.10.2026. материалы автора
  14. [14]Press W. H., Teukolsky S. A., Vetterling W. T., Flannery B. P. Numerical Recipes: The Art of Scientific Computing. Cambridge University Press (разд. 9.7 — метод Бройдена; на раздел ссылается исходный код Sim42 [13]). вторичный

Глоссарий

Непоследовательно-модульный солвер (non-sequential modular)
модульный солвер, распространяющий информацию вперёд и назад по схеме, так что данные можно задавать в любой точке
Аппарат (unit operation)
любой объект схемы с правилами расчёта; поток — тоже аппарат
Порт (port)
точка аппарата, где хранятся значения и через которую они передаются соседям
Статус значения
метка происхождения значения: задано, рассчитано, передано, оценка, неизвестно
Степени свободы
число величин, которые нужно задать, чтобы схема считалась однозначно
Спецификация
значение, заданное пользователем (статус FIXED)
Флэш (flash)
расчёт фазового равновесия и свойств по двум независимым из (T, P, H…) и составу
Забывание (forget)
фаза инвалидации: стирание устаревших рассчитанных значений без графа зависимостей
Рецикл / поток разрыва (tear stream)
замкнутый контур в схеме; точка, в которой задаётся оценка для итераций
Метод Бройдена
квази-ньютоновский метод решения систем нелинейных уравнений без расчёта производных
Масштаб переменной (scaleFactor)
величина, на которую нормируются ошибки, чтобы разные переменные сравнивались справедливо
hold / go
команды приостановки и возобновления расчёта