В этом видео получена система. Решим эти уравнения связного графа, применив Python, в частности библиотеки numpy, scipy и matplotlib. Построим графики решения.
Скачать только скрипт.
Скачать целиком виртуальное окружение Python (Jupiter Notebook и все библиотеки).
Имя архива виртуального окружения: HydraulicEquationsSolve01.zip
Размер: 195841054 байтов (186 MiB)
CRC32: 836DFC91
Исходный код в виртуальном окружении приложен на Яндекс-диске. Это сделано для удобства. Можно просто скачать, запустить виртуальное окружение и редактировать код в Jupyter Notebook.
О решении системы уравнений связного графа
На всякий случай, напомним. Система состоит из сопротивлений, двух баков и одной длинной трубы, у которой нельзя пренебречь инерцией.
О неточностях. У трубы ‘lf’ должно быть сопротивление. В задаче учитывается только инерция. Более в задаче трубопроводов нет. Соединения между баками и дросселями — идеальные. Повторю здесь картинку со схемой из видео.

Система решается путем создания функции, вычисляющей производные. То есть, во-первых надо записать уравнения системы соответственно, а во-вторых, надо сделать функцию, которая их вычисляет и скормить эту функцию решателю вместе с начальными условиями. Да, и не следует забывать и о том, что одно из уравнений алгебраическое.
Кстати, решатель меняется очень легко. Попробуйте поменять BDF на RK45 в вызове solve_ivp( ). Получится шум в давлении. Это из-за того, что давление определяется дифференцированием момента Г. Говорят, что это т.н. жёсткая система. С решателем BDF картинка более гладкая.
Код — Расчет констант
Здесь, согласно параметрам трубопровода, баков и дросселей мы считаем сопротивления, емкости и индуктивность. В соответствии с этой статьей.
Значения выбраны так, чтобы расход через второй дроссель был минимальным. Его диаметр 1 мм. Вся рабочая жидкость уходит в баки. Это позволяет провести небольшую проверку. Сумма объемов в баках должна быть равна объему подачи от источника. Давление в каждом баке определяется высотой столба жидкости. Давление в первом падает на первом дросселе R1 но не всё, а до уровня давления во втором баке. На дросселе R2 должно падать все давление из второго бака.import numpy as np import numpy as np import pandas as pd from scipy.integrate import solve_ivp import matplotlib.pyplot as plt import csv # ------------------ Define constants -------------------- mu = 1e-3 # Pa·s (dynamic viscosity of water) rho = 1000 # kg/m^3 (density of water) g = 9.81 # m/s^2 (gravitational acceleration) # Tank, pipe, throttles dimensions D1 = 0.03 # m (diameter of the long pipe) Dthr1 = 0.03 # m (diameter of throttle R1) Dthr2 = 0.001 # m (diameter of throttle R2) Dt1 = 0.5 # m (C1 tank diameter) Dt2 = 0.5 # m (C2 tank diameter) L = 200 # m (length of the long pipe) # Flow rate in m^3/s from the source. q = 100 / 60 / 1000 # 100 liters/min converted to m^3/s # Cross-sectional areas of the tanks. A_tank_1 = np.pi * Dt1**2 / 4 # Area of C1 A_tank_2 = np.pi * Dt2**2 / 4 # Area of C2 print('{0}, {1}'.format(A_tank_1, A_tank_2)) # Calculate orifice resistances. R1 = (128 * mu * L) / (np.pi * Dthr1**4) R2 = (128 * mu * L) / (np.pi * Dthr2**4) # Calculate capacities of the tanks. C1 = A_tank_1 / (rho * g) C2 = A_tank_2 / (rho * g) # Calculate hydraulic inertia of the liquid in the pipe. ALP = np.pi * D1**2 / 4 lf = rho * L / ALP
Код — Решение системы уравнений
Система уравнений решается численно. Используется встроенная функция библиотеки SciPy, которая называется solve_ivp( ). Подход здесь стандартный для библиотечных численных методов. Система выражается в виде функции, подходящей для алгоритма. А именно, функция дает значения производных в зависимости от значений искомых величин.
Величин три. Объем в первом баке, во втором и гидравлический момент в длинной трубке, которая имеет инерцию.
# Create Pandas dataframe to store results data = pd.DataFrame(0, index=range(1), columns=['Time', 'PC1', 'PR1', 'QR1', 'PC2', 'Qlf', 'PR2']) # Define the system of DAEs def hydraulic_system(t, y): global data V1, V2, G = y # V1 is volume in C1, V2 is volume in C2, G is Gamma # Ptimary conditions (PC) in junctions. PC1 = (V1 / C1) # PC in 0-junction for C1. PR1 = (V1 / C1) - (V2 / C2) # Algebraic equation for PR1 (over throttle R1). QR1 = PR1 / R1 # PC in 1-junction for R2. PC2 = (V2 / C2) # PC in 0-junction for C2. Qlf = G / lf # PC in 1-junction for R2 and pipe. PR2 = Qlf * R2 # Calculation for PR2 drop. # Save the calculated PC and PR1 to the dataframe. new_row = pd.DataFrame({'Time': [t], 'PC1': [PC1], 'PR1': [PR1], 'QR1': [QR1], 'PC2': [PC2], 'Qlf': [Qlf], 'PR2': [PR2]}) if new_row.notna().all(axis=None): data = pd.concat([data, new_row], ignore_index=True) # Differential equations V1_dot = q - PR1 / R1 V2_dot = PR1 / R1 - G / lf G_dot = V2 / C2 - G * R2 / lf return [V1_dot, V2_dot, G_dot] # Initial conditions: (initial volumes V1, V2, and G) V1_0 = 0 # initial volume in tank C1 V2_0 = 0 # initial volume in tank C2 G_0 = 0 # initial Gamma (pressure in pipe) # Time span for the solution process_time = 60 # seconds t_span = (0, process_time) # time span in seconds y0 = [V1_0, V2_0, G_0] # Solve the system using solve_ivp sol = solve_ivp(hydraulic_system, t_span, y0, method='BDF', dense_output=True)
Постпроцессинг некоторых величин
# Time points for plotting t = np.linspace(0, process_time, 1000) y = sol.sol(t) # Postprocessing Gamma # Calculate Gamma' (numerical derivative of Gamma) Gamma = y[2] Gamma_prime = np.gradient(Gamma, t) # Numerical derivative of Gamma # Calculate pressure difference pressure_difference = Gamma_prime
Графики результата
# Create a figure and a set of subplots fig, axes = plt.subplots(7, 1, figsize=(10, 25)) # Plot V1 (Volume in C1) axes[0].plot(t, y[0], label='V1 (Volume in C1)', color='b') axes[0].set_ylabel('Volume') axes[0].set_title('V1 (Volume in C1)') axes[0].legend() axes[0].grid(True) # Plot V2 (Volume in C2) axes[1].plot(t, y[1], label='V2 (Volume in C2)', color='g') axes[1].set_ylabel('Volume') axes[1].set_title('V2 (Volume in C2)') axes[1].legend() axes[1].grid(True) # Plot Gamma (Pressure in pipe) axes[2].plot(t, pressure_difference, label='dPi (Pressure diff in pipe)', color='r') axes[2].set_ylabel('Pressure') axes[2].set_title('dPi (Pressure diff in pipe)') axes[2].legend() axes[2].grid(True) # Plot calculated parameter PR1. axes[3].plot(data['Time'], data['PR1'], label='PR1 (delta over R1 throttle)', color='b') axes[3].set_ylabel('Pressure drop') axes[3].set_title('Pressure drop PR1') axes[3].legend() axes[3].grid(True) # Plot calculated parameter PR2. axes[4].plot(data['Time'], data['PR2'], label='PR2 (delta over R2 throttle)', color='r') axes[4].set_ylabel('Pressure drop') axes[4].set_title('Pressure drop PR2') axes[4].legend() axes[4].grid(True) # Plot QR1 axes[5].plot(data['Time'], data['QR1'], label='QR1', color='b') axes[5].set_ylabel('QR1') axes[5].set_title('QR1 flow rate') axes[5].legend() axes[5].grid(True) # Plot Qlf axes[6].plot(data['Time'], data['Qlf'], label='Qlf', color='g') axes[6].set_ylabel('Qlf') axes[6].set_title('Qlf flow rate through pipe and R2') axes[6].legend() axes[6].grid(True)
Проверки
# Print check results print('Check:') print('Total volume by source = {0}(m^3).'.format(q*process_time)) print('V1 = {0}(m^3) V2 = {1}(m^3), sum = {2}'.format(y[0][-1], y[1][-1], y[0][-1] + y[1][-1])) print('Pressure in the C2: from liquid level {0}, pressure in the pipe {1}'.format(rho*g*(y[1][-1]/A_tank_2), data['PC2'].iloc[-1]))
Повторюсь, в самом начале, мы установили диаметр дросселя R2 1 мм. Это сделано, чтобы вся РЖ пошла в баки.
Поэтому, по сумме объемов в баке и имея заданный расход источника, можно проверить расходы. Проверка по конечному значению (на 60-й секунде). Давление проверяется иначе, нужно считать гидростатическое давление баков. Оно падает на дросселях и трубе.
Давление в первом баке определяется высотой столба РЖ. Для приведенных графиков (t=60c),
, тогда
и эта высота столба соответствует давлению
Па.
Аналогично из уровня во втором баке можно получить высоту 0,061 м, которая соответствует 600 Па. Примерно эти цифры и падают на графиках Pressure Drop PR1 и Pressure Drop PR2, там они рассчитаны из расходов.
Графики


