Pull to refresh

Создание Minimum Viable Product в сфере энергетики за 1,5 года командой инженеров без навыков программирования

Reading time11 min
Views2.5K
Кожухотрубчатый теплообменник
Кожухотрубчатый теплообменник

0. Что имеем на старте

  1. Компания L****, находясь, в непростой для себя точке решает изменить модель бизнеса и принимает решение пойти в область IT. За 10 лет компания спроектировала и запустила в работу более 90 кожухотрубчатых теплообменников. На базе данного опыта решено было создать облачный сервис для автоматизированного подбора и проектирования теплообменных аппаратов.

  2. Из первоначального штата компании (более 80 человек) в группу разработки вошли 6 человек:

    1. Инженер-конструктор 1-ой категории (есть небольшой опыт на VBA)

    2. Инженер-теплотехник 2-ой категории (без опыта программирования)

    3. Инженер-теплотехник 3-ий категории (без опыта программирования)

    4. Инженер-физик (без опыта программирования)

    5. Инженер-химик (подключился к проекту через полгода, годовой курс по Python)

    6. Инженер-математик (есть опыт программирования на PHP)

1. Подготовка

1.1. Выбор стека

Руководителем группы был выбран Инженер-математик, обладавший на тот момент наибольшим опытом программирования. Им и было предложено в качестве языка программирования был выбрать Python по следующим ключевым причинам:

  1. Всем из команды очень понравилась философия Python :)

  2. Простота синтаксиса и читаемость кода

  3. Хорошая поддержка ООП

  4. Наличие большого количества готовых прикладных библиотек

В качестве среды разработки был выбран JupyterLab, тогда еще версии 2.0:

JupyterLab 2.0
JupyterLab 2.0

В качестве базы данных выбрали MySQL, поскольку у руководителя проекта в ней тоже был опыт работы.

1.2. Первый план на полгода

Первым шагом было создание плана разработки на полгода. Создан был план в корпоративной MediaWiki:

План реализации проекта до конца 2020 года
План реализации проекта до конца 2020 года

2. Обучение Python

Поскольку навыков программирования в Python не было ни у одного члена команды мы начали с самых азов.

В качестве ориентиров были бесплатный курс по Python от Яндекса и курс лекций от Тимофея Хирьянова.

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

Пример из урока по функциям
# Одна или несколько переменных в функции могут быть заданы по умолчанию
# Но сначала в функции должны быть перечислены обязательные аргументы

def F(Q = 1.5, dT = 10, k = 1600):
    return Q*10**6/(k*dT)

print(F())
print(F(1.5,14))
print(F(dT=14, Q=1.5))
print(F(Q=1.5, dT=14))  # к берется по умолчанию равным 1600
print(F(Q=1.5, dT=14, k=2000))

Пример домашнего задания
# Ниже подготовлен массив свойств воды: 
# [tºС, ρ кг/м³, ср Дж/(кг×К), λ Вт/(м×К), μ н×с/м²]

WP = [
[1,1000,4218,0.564,0.001790],
[5,1000,4208,0.572,0.001549],
[10,1000,4196,0.582,0.001306],
[15,999,4191,0.591,0.001160],
[20,999,4185,0.600,0.001002],
[25,997,4183,0.608,0.000900],
[30,996,4180,0.615,0.000798],
[35,994,4180,0.622,0.000726],
[40,993,4179,0.629,0.000653],
[45,991,4180,0.635,0.000600],
[50,988,4180,0.641,0.000547],
[55,986,4172,0.646,0.000507],
[60,984,4183,0.651,0.000467],
[65,981,4186,0.656,0.000436],
[70,978,4188,0.660,0.000404],
[75,975,4192,0.664,0.000379],
[80,972,4196,0.667,0.000355],
[85,969,4201,0.670,0.000335],
[90,966,4205,0.673,0.000315],
[95,962,4211,0.676,0.000298],
[100,959,4217,0.678,0.000282],
[110,951,4230,0.682,0.000255],
[120,944,4246,0.684,0.000232],
[130,935,4265,0.685,0.000213],
[140,926,4286,0.685,0.000197],
[150,917,4310,0.684,0.000183],
[160,908,4338,0.682,0.000170],
[170,898,4369,0.679,0.000160],
[180,887,4406,0.675,0.000150],
[190,876,4447,0.669,0.000142],
[200,865,4494,0.663,0.000134],
[210,853,4548,0.656,0.000128],
[220,840,4611,0.648,0.000122],
[230,827,4683,0.639,0.000116],
[240,814,4767,0.629,0.000111],
[250,799,4865,0.618,0.000106],
[260,784,4981,0.606,0.000102],
[270,768,5120,0.592,0.000098],
[280,751,5290,0.578,0.000094],
[290,732,5490,0.562,0.000090],
[300,712,5750,0.545,0.000086]
]

print(WP[20][2])

# Задача: Написать функцию сведения теплового баланса
# с любым одним необязательным параметром

# Исходные данные (запрашиваются у пользователя)
# t1, Цельсий - температура на входе в трубное
# t2, Цельсий - температура на выходе в трубное
# T1, Цельсий - температура на входе в межтрубное
# T2, Цельсий - температура на выходе в межтрубное
# g, кг/час - расход среды в трубном
# G, кг/час - расход среды в трубном

# Вывод программы:
# t1 труб. вход = 
# t2 труб. выход = 
# T1 межтруб. вход = 
# T2 межтруб. выход = 
# g труб. = 
# G межтруб. = 
# Q труб. = 
# Q межтруб. = 

3. Начало работы

На момент старта проекта в компании L**** был реализован механизм расчета теплообменника в виде Excel файла. Вот его вид:

Расчет кожухотрубчатого теплообменника в Excel
Расчет кожухотрубчатого теплообменника в Excel
Пример логики в данном расчете :)
Пример логики в данном расчете :)

Для перевода данного расчета на Python следовало решить ряд подготовительных задач:

  • Оцифровать графики TEMA для определения поправки Z

  • Построить коммуникацию через API с пакетом теплофизических свойств для решения задачи по сведению теплового баланса

  • Занести в базу данных таблицы из ГОСТа: 34347-2017 сосуды и аппараты стальные сварные. общие технические условия / 34347 2017

3.1. Оцифровка графиков TEMA

Вид графика из TEMA

Итоговый результат оцифровки
Для каждого графика получается такая таблица в Excel
Для каждого графика получается такая таблица в Excel

Результат перевода в массивы Python
Mas_7_11 = [
  [
    0.1,
    [
      [1.000000000000000, 0.499999999999999],
      [0.999322833180564, 0.613635531736094],
      [0.997093063158007, 0.664615593443683],
      [0.996832262423495, 0.737096845531233]
    ]
  ]
]

3.2. Связь по API с пакетом теплофизических свойств

Для сведения теплового баланса необходимо знать свойства сред для обоих теплоносителей.

В мире одним из самых крупных пакетов свойств является пакет компании Aspen Technology Inc. - Aspen Properties.

У данного продукта есть хорошо документированный API и к нему мы и решили подключаться для получения нужных нам свойств.

Для реализации этой задачи был выбран подход в виде отдельного HTTP-API на базе Flask. Сервер принимает запросы и через API Aspen Properties запрашивает нужные свойства. Данный сервер работает на стороне клиента, так как требуется соответствующая лицензия на Aspen Properties.

3.3. Подготовка базы данных по ГОСТам

Как я уже говорил выше мы выбрали базу данных MySQL и в неё требовалось занести все табличные параметры из ГОСТов, используемых при проектировании нашего оборудования.

Как выяснится намного позже более эффективно будет преобразовать все талицы в Pandas dataframe'ы и работать с ними.

Вид таблиц в базе данных, содержащих ГОСТовские занчения
Вид таблицы из ГОСТ в базе данных MySQL
Вид таблицы из ГОСТ в базе данных MySQL

4. Первые функции

С момента начала обучения прошло примерно 2 месяца и у нас начали появлятся уже первые модули и функции.

Функция отрисовки фланца
import drawSvg as draw

def FlangeDrawing (D, D_1, D_2, D_6, d, n, d_1, D_m, D_n, H, H_1, b, h):
    # Создаем холст размеров D+50
    td = draw.Drawing(D+50, H+100, origin='center', displayInline=False)
            
    #Рисуем оси
    td.append(draw.Line(-D_1/2, h, -D_1/2, -b-2,style="stroke-dasharray: 20 5 2 5", stroke='#CC0000', stroke_width=0.75, fill='none'))
    td.append(draw.Line(D_1/2, h, D_1/2, -b-2,style="stroke-dasharray: 20 5 2 5", stroke='#CC0000', stroke_width=0.75, fill='none'))
    td.append(draw.Line(0, h, 0, -H, stroke='#CC0000',style="stroke-dasharray: 20 5 2 5", stroke_width=0.75, fill='none'))
    
    #Рисуем  тарелку фланца
    td.append(draw.Rectangle(-D/2,-b, D, b, fill='none',stroke_width=2,stroke='blue'))
    
    #Рисуем  уплотнительную поверхность фланца
    td.append(draw.Lines(-D_2/2-3, 0,
                    -D_2/2, h,
                    D_2/2, h,
                    D_2/2+3, 0,    
                    close=False,
            fill='none',stroke_width=2,
            stroke='blue'))
    #Рисуем присоединительные втулку фланца
    td.append(draw.Lines(-D_m/2, -b,
                    -D_n/2, -(H-H_1-h),
                    -D_n/2, -(H-h),
                    D_n/2, -(H-h),
                    D_n/2, -(H-H_1-h),
                    D_m/2, -b,     
                    close=False,
            fill='none', stroke_width=2, 
            stroke='blue'))
    
    return td
    
FlangeDrawing(250, 240, 212, 204, 22, 8, 146, 180,161, 60, 10, 22, 3)

Функция расчета входной группы
def ShellEntrance(VolumeFlow, D_nozzle, D_inside, OTL, D_tube, step_tube, plate, S_plate, entrance_baffle_spacing, h1, h2, F2, K):
    if ((D_inside - OTL) / 2) > ((h1 + h2) / 2):
        h = (D_inside - OTL) / 2
    else:
        h = (h1 + h2) / 2
    
    if plate == False:
        plate_k = 1
    else:
        plate_k = 0
        
    As = ((3.14159 * D_nozzle * h) + (plate_k * 0.785 * D_nozzle * D_nozzle * (step_tube - D_tube) / F2 / step_tube)) / 1000000
    Vs = VolumeFlow / As
    
    if D_nozzle > K:
        k = D_nozzle
    else:
        k = K
    
    Ab = (entrance_baffle_spacing * (D_inside - OTL) + (entrance_baffle_spacing * (k - D_tube) - S_plate) * (step_tube - D_tube) / F2 / step_tube) / 1000000
    Vb = VolumeFlow / Ab
    
    return [Vs, Vb]
    
ShellEntrance(0.0192, 200, 500, 470, 16, 21, False, 0, 350, 78, 67, 1, 331)

Расчет температурного напора
def LMTD_countercurrent(t1, t2, T1, T2):
    max_TD = max(T1-t2, T2-t1)
    min_TD = min(T1-t2, T2-t1)
    countercurrent_LMTD = (max_TD - min_TD)/math.log(max_TD/min_TD)
    return countercurrent_LMTD

По мере освоения возможностей JupyterLab мы добрались до виджетов ipywidgets.

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

Интерактивный виджет в JupyterLab для построения разбивок
Визуализация построения разбивок и расчета зазоров.
Визуализация построения разбивок и расчета зазоров.

5. Flask для HTTP API

Изначально мы не думали про реализацию нашей системы в виде отдельного API. Но по мере продвижения проекта этот подход возник сам по себе.

Мы выбрали Flask в качестве веб-фреймворка для реализации API потому что:

  1. Легковестность.

  2. Отсутствие в проекте асинхронности.

  3. Большое комьюнити.

6. MVP 1.0 на базе Anvil.Works

Вопрос о платформе на которой мы будем делать MVP модель был долгое время открыт.

В команде разработки не было людей с опытом в HTML, CSS, JavaScript. Поэтому мы искали вариант конструктора интерфейса и желательно с поддержкой Python.

Сервис Anvil.Works был найден можно сказать случайно и сначала не рассматривался как полноценный вариант для создания пользовательского интерфейса.

Интерфейс среды разработки Anvil.Works
Интерфейс среды разработки Anvil.Works

Дело в том, что изначально Anvil.Works давал возможность запускать проект только в своем облаке. При этом на бесплатном тарифном плане скорость работы интерфейса при запросах к внешнему API была крайне низкая. А запросов у нас получилось довольно много.

В конце 2021 года на нашу удачу сервис Anvil.Works объявил о бесплатной возможности запустить свой проект на своем сервере. Это было для нас идеальное решение.

Вид запущенного приложения
Вид запущенного приложения

7. Git

Как бы это не звучало странно в 2022 году, но GIT у нас появился далеко не сразу. Мы работали в единой среде JupyterLab и у каждого была своя рабочая папка.

Но как только мы запустили первую версию приложения на Anvil.Works и стали тестировать некоторые функции нашего API мы поняли что нам нужна система совместного версионного контроля.

На тот момент (2020-2021) в GitHub уже были бесплатные закрытые репозитории и мы выбрали его.

8. Алгоритмы поиска решения

8.1. Генетический алгоритм

8.1.1. Генетическая игра "Мир кроликов"

Идея применения генетического алгоритма для поиска конструкции аппаратов пришла можно сказать случайно.

Первопричиной стало вот это видео:

После просмотра этого видео было решено реализовать свой вариант вселенной с такими условиями для того чтобы команда стала лучше чувствовать модель ООП.

В итоге получилась своя вселенная, которую мы ласково назвали "Мир кроликов".

Генетическая игра "Мир кроликов"

Ссылка на GitHub: https://github.com/tau15/Rabbit_World

Симуляция мира кроликов
Симуляция мира кроликов
Устройство кролика :)
Внутри каждого кролика заложена программа (цепочка ДНК), представляющая собой последовательность действий кролика в игровой вселенной.
Внутри каждого кролика заложена программа (цепочка ДНК), представляющая собой последовательность действий кролика в игровой вселенной.

Параметры мира кроликов
# World values
DNA_commands = 32 # Размерность генов в ДНК
Food_limit = 100 # Кол-во еды, генерируемое для каждого нового поколения
Poison_limit = 0 # Кол-во ядовитой еды
Food_generation = 200 # Кол-во еды, генерируемое в текущем поколении
sleep_time = None # Задержка перед выполнением ДНК кролика
Bots_num = 8 # Число кроликов в покалении
DNA_lenght = 32 # Кол-во генов в ДНК
Survivers = 2 # Число выживающих в каждом поколении
Mutation = 1 # Кол-во мутантов в поколении
Love = True # Смешение ДНК двух кроликов
Mutation_dna = 2 # Число мутаций в ДНК

8.1.2. Генетический алгоритм для теплообменников

В новогодние праздники 2020-2021 возникла идея применить генетический алгоритм к задаче подбора конструкции теплообменного аппарата.

В качестве критерия отбора в каждом поколении был выбран коэффициент теплопередачи аппарата - K, который по сути является сводным показателем эффективности конструкции теплообменного аппарата.

Класс, описывающий ДНК теплообменника
class STHE_DNA():
    
    Gens = {
        'ShellType': [['E', 'F'], 0.5], # Тип кожуха
        'TubePassNum' : [[1, 2, 3, 4, 6, 8, 10, 12, 14, 16, 18, 20], 0.5], # Количество ходов по трубному
        'CaseNum' : [[1, 2, 3, 4, 5, 6], 0.5], # Колличество корпусов
        'TDiam' : [[10, 12, 16, 20, 25, 32, 38, 57], 0.5], # Диаметр труб
        'tube' : [['HOT', 'COLD'], 0.5], # Тип среды в трубах
        'PatternCharacter' : [['Треугольники', 'Повернутые треугольники', 'Коридорные квадраты', 'Квадраты'], # Характер разбивки 0.5],
        'BufflesType' : [['segm', 'screw'], 0.5], # Тип перегородок
    }
    
   ...
   ...

Класс, описывающий возможные мутации в генах

class MUTATE_DNA():
    
    Gens = {
        'ShellType': [0, 1],
        'TubePassNum' : [0, 1],
        'CaseNum' : [0, 1],
        'TDiam' : [0, 1],
        'tube' : [0, 1],
        'PatternCharacter' : [0, 1],
        'BufflesType' : [0, 1],
        'TubeNum' : [0, 1, 0.3, 0.5, 0.7, 0.9],
        'TubeNum_direction' : [1, -1],
        'TubeLenght' : [0, 1, 0.3, 0.5, 0.7, 0.9],
        'TubeLenght_direction' : [1, -1]
    }
    
    ...
    ...

Функция генерации нового поколения теплообменников
#  DNA_array - 
# MUTATE_DNA_array - 

def BuildNewGeneration(self, DNA_array, MUTATE_DNA_array):

        # Добавляем текущие DNA к новому поколению
        for i in DNA_array:
            pass
            
        for i in DNA_array:
            for m in MUTATE_DNA_array:
                
                # 3 близких варианта
                for n in range(1):
                    pass    
                
                # 3 близких варианта
                for n in range(3):
                    pass

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

График корреляции массы аппарата и коэффициента теплопередачи К
На графиках видна обратная зависимость коэффициента теплопередачи и массы аппарата.
На графиках видна обратная зависимость коэффициента теплопередачи и массы аппарата.

8.2. Сеточный алгоритм

В генетическом алгоритме был для нас критически не приемлемый момент - нестабильность итогового решения.

Поэтому мы приняли решение строить сетку всех возможных решений и затем выбирать наиболее подходящий вариант.

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

Блок-схема алгоритма построения сетки решений
На схеме показан алгоритм поиска решения, если пользователь не задал строго тип кожуха.
На схеме показан алгоритм поиска решения, если пользователь не задал строго тип кожуха.

9. Механизм выбора лучшего варианта

Основными критериями для выбора лучшего варианта стали:

  1. МАССА. Чем меньше масса аппарата, тем дешевле аппарат. Выбор наименьшего возможного значения массы из числа всех вариантов и расчет отклонения в долях единицы от наименьшего значения массы для каждого варианта.

  2. КОЭФФИЦИЕНТ ТЕПЛОПЕРЕДАЧИ. Характеризует интенсивность процесса теплопередачи, чем больше коэффициент теплопередачи, тем эффективней работает аппарат. Выбор наибольшего возможного значения коэффициента теплопередачи из числа всех вариантов и расчет отклонения в долях единицы от наибольшего значения коэффициента теплопередачи для каждого варианта.

  3. РЕЙНОЛЬДС ТРУБНОГО ПРОСТРАНТСВА. Для грязных сред имеет значение отдельно этот параметр. Чем больше значение числа Рейнольдса трубного просранства (в пределах допустимой скорости), тем меньше вероятность образования отложений и загрязнения аппарата. Выбор наибольшего возможного значения числа Рейнольдса трубного просранства из числа всех вариантов и расчет отклонения в долях единицы от наибольшего значения числа Рейнольдса трубного для каждого варианта.

  4. РЕЙНОЛЬДС МЕЖТРУБНОГО ПРОСТРАНТСВА. Для грязных сред имеет значение отдельно этот параметр. Чем больше значение числа Рейнольдса межтрубного просранства (в пределах допустимой скорости), тем меньше вероятность образования отложений и загрязнения аппарата. Выбор наибольшего возможного значения числа Рейнольдса межтрубного просранства из числа всех вариантов и расчет отклонения в долях единицы от наибольшего значения числа Рейнольдса межтрубного для каждого варианта.

10. MVP 2.0

Как мне кажется в итоговой версии интерфейса мы раскрыли почти весь потенциал Anvil.Works:

Раздел "Проекты"
Раздел "Проекты"
Раздел "Режимы работы"
Раздел "Режимы работы"
Раздел "Требования к конструкции"
Раздел "Требования к конструкции"
Раздел "Задачи и расчеты"
Раздел "Задачи и расчеты"
Раздел "Отчеты"
Раздел "Отчеты"

11. Эскизный проект

Задача по построению эскизного проекта казалась на старте трудно реализуемой, но по итогу мы получили вполне приемлемый модуль, который строит вот такие чертежи:

Эскизный проект кожухотрубчатого теплообменника AES
Эскизный проект кожухотрубчатого теплообменника AES

12. Анализ разбивок с помощью OpenCV

В 2021 году все члены нашей команды выразили желание пройти обучение по курсу "Data Science и нейронные сети".

Как результат, появился модуль анализа разбивки аппарата по фотографии или чертежу.

13. Расчет теплообменника по зонам

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

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

Визуализация процесса расчета в JupyterLab

Дальнейшие планы:

  • Динамический шаг перегородок в межтрубном пространстве с учетом фазовых переходов.

  • Ускорение работы сеточного алгоритма.

  • Расчет вибраций.

  • Добавление других типов кожухов.

  • Перевод на английский язык.

  • Добавление стандартов ASME, PED.

  • Формирование 3D модели аппарата в CAD системе.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 4: ↑4 and ↓0+4
Comments10

Articles