Определение значений расходов в участках гидравлической сети

Рассмотрим гидравлическую сеть:
title

$H$, м - напор насоса, $R$ - гидравлическое сопротивление участка сети (подробнее про гидравлическое сопротивление см. в статье Моделирование характеристики гидравлической сети), $Q$, $м^3/ч$ - объёмный расход через участок сети.

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

Для этого следует составить и решить систему нелинейных уравнений.

Рассмотрим сеть, состоящую из трёх потребителей. В этом случае нужно найти пять значений расходов: $Q_i,\ i = 0 \ldots 4$, для чего необходимо составить и совместно решить пять уравнений.

$$H - (R_0 + R_2)Q_0^2 - R_1Q_1^2 = 0$$$$R_1Q_1^2 - (R_3 + R_5)Q_2^2 - R_4Q_3^2 = 0$$$$R_4Q_3^2 - (R_6 + R_7 + R_8)Q_4^2 = 0$$$$Q_0 - Q_1 - Q_2 = 0$$$$Q_2 - Q_3 - Q_4 = 0$$
In [1]:
import numpy as np #работа с массивами
N = 3 # количество потребителей

Значения гидравлических сопротивлений $R$ участков сети и значение напора насоса $H$ возьмём отсюда.

In [2]:
#Зададим значения гидравлических сопротивлений всех участков сети
Rs = np.array([[0.0002, 0.0002, 0.0002],
              [0.0004, 0.0004, 0.0004],
              [0.0005, 0.0005, 0.0005]], dtype = float)
#Напор насоса, м
H = 41.31
In [3]:
#Т.к. в функцию поиска корней системы нелинейных уравнений в качестве
#входного параметра передаётся одномерный массив значений первых приближений расходов,
#то все массивы придётся делать одномерными
Rs = np.reshape(Rs, N * 3, order = 'F')
Rs
Out[3]:
array([0.0002, 0.0004, 0.0005, 0.0002, 0.0004, 0.0005, 0.0002, 0.0004,
       0.0005])
In [4]:
#Функция, возвращающая результат вычисления входящих в систему уравнений выражений
#для переданных значений расходов Qs. Если Qs - решение системы уравнений, то
#функция возвращает массив из (2*N-1) нулей.
def func(Qs):
    f = np.zeros(2 * N - 1)
    #Выражение для нулевого контура - падение давления на участке R0-R1-R2 должно равняться H
    f[0] = H - (Rs[0] + Rs[2]) * Qs[0] ** 2 - Rs[1] * Qs[1] ** 2
    #Выражения для контуров 1 - (N-2)
    for i in range(1, N-1):
        j = i * 3
        k = i * 2
        f[i] = Rs[j-2] * Qs[k-1]**2 - (Rs[j] + Rs[j+2]) * Qs[k] ** 2 - Rs[j+1] * Qs[k+1] ** 2
    #Выражения для контуров N-1    
    j = (N-1) * 3
    k = (N-1) * 2
    f[N-1] = Rs[j-2] * Qs[k-1]**2 - (Rs[j] + Rs[j+1] + Rs[j+2]) * Qs[k] ** 2
    #Баланс расходов в узлах 0 - (N-2)
    for i in range(N, N + N - 1):
        k = (i - N) * 2
        f[i] = Qs[k] - Qs[k+1] - Qs[k+2]
    return f

В функцию нахождения решения системы нелинейных уравнений необходимо передавать в качестве параметра массив первых приближений значений расходов. Если значения первых приближений выбраны неудачно, то функция может не найти решение (выдать ошибку) или уйти в область отрицательных значений.

Для формирования массива первых приближений проделаем следующее. Расчитаем расход через каждый потребитель при условии отключения от подводящего и отводящего трубопроводов других потребителей кроме рассматрваемого. Затем найдём значения расходов через участки подводящего трубопровода при условии, что через каждый потребитель проходит найденный на первом этапе расход.

In [5]:
from math import sqrt
#Функция для определения начальных значений, передаваемых 
#в функцию решения системы нелинейных уравнений в качестве первого приближения
def get_init_guess():
    Qs_i = np.zeros(2*N-1)
    #опрределяем расход через каждый потребитель
    R_pipe = 0
    for i in range(N-1):
        j = i * 3
        R_pipe += Rs[j] + Rs[j+2] #сопротивление подводящих и отводящих трубопроводов
        R_circ  = R_pipe + Rs[j+1] #суммарное сопротивление контура
        Qs_i [i * 2 + 1] = sqrt(H/R_circ) #расход через контур
    #Для последнего контура
    j = 3 * (N-1)
    k = 2 * (N-1)
    Qs_i[k] = sqrt(H / (R_pipe + Rs[j] + Rs[j+1] + Rs[j+2]))
    #Отределяем расход через участки подводящего трубопровода
    Q = Qs_i[k]
    for i in range(N-2, -1, -1):
        Qs_i[2*i] = Q + Qs_i [i * 2 + 1]
        Q = Qs_i[2*i]
    return Qs_i
In [6]:
get_init_guess()
Out[6]:
array([473.82823768, 193.78995189, 280.03828579, 151.49257408,
       128.54571171])

Для решения систем нелинейных уравнений применим функцию root из библиотеки scipy.optimize.

In [7]:
from scipy.optimize import root
#Вызываем функцию root, передав ей функцию с уравнениями и массив начальных приближений 
sol = root(func, get_init_guess())
#Значения найденных расходов находится в sol.x
sol.x
Out[7]:
array([221.61767816, 131.62373749,  89.99394067,  56.14015402,
        33.85378665])
In [8]:
#Количество выполненных приближений
sol.nfev
Out[8]:
21

Проверим правильность найденных в [1] сбалансированных значений гидравлических сопротивлений линий потребителей.

In [9]:
Rs_b = np.array([0.008432, 0.0275, 0.0004])
for i in range(Rs_b.shape[0]):
    Rs[i*3+1] = Rs_b[i]
r = root(func, get_init_guess())
r.x
Out[9]:
array([170.        ,  50.00000001, 120.        ,  20.        ,
        99.99999999])

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

In [10]:
N = 50 #Попробуем сделать расчёт для сети с 50 потребителями
Rs = np.random.sample(N * 3) + 1e-4 #Добавляем 1e-4 чтобы гарантированно не было нулевых сопротивлений
H = 1000 #Задаём напор насоса
sol = root(func,get_init_guess())
sol.x
Out[10]:
array([2.85935083e+01, 1.62095851e+01, 1.23839232e+01, 8.23417608e+00,
       4.14974711e+00, 2.33190642e+00, 1.81784069e+00, 1.20293802e+00,
       6.14902668e-01, 3.73733993e-01, 2.41168675e-01, 2.08645815e-01,
       3.25228602e-02, 1.73955191e-02, 1.51273411e-02, 1.05805562e-02,
       4.54678486e-03, 2.87054677e-03, 1.67623808e-03, 9.56473355e-04,
       7.19764727e-04, 3.97977279e-04, 3.21787448e-04, 2.07539122e-04,
       1.14248326e-04, 6.33218807e-05, 5.09264449e-05, 3.52486679e-05,
       1.56777770e-05, 6.76603533e-06, 8.91174171e-06, 5.47652978e-06,
       3.43521194e-06, 2.65674506e-06, 7.78466879e-07, 4.73029870e-07,
       3.05437008e-07, 1.93283170e-07, 1.12153838e-07, 4.46223718e-08,
       6.75314685e-08, 4.30907832e-09, 6.32223902e-08, 2.61015569e-09,
       6.06122345e-08, 2.55619925e-09, 5.80560353e-08, 2.49488916e-09,
       5.55611461e-08, 2.46340878e-09, 5.30977374e-08, 2.45348426e-09,
       5.06442531e-08, 2.40096022e-09, 4.82432929e-08, 2.36339033e-09,
       4.58799025e-08, 2.31251650e-09, 4.35673860e-08, 2.31836121e-09,
       4.12490248e-08, 2.25849414e-09, 3.89905307e-08, 2.26209355e-09,
       3.67284371e-08, 2.24794373e-09, 3.44804934e-08, 2.23864545e-09,
       3.22418480e-08, 2.23160917e-09, 3.00102388e-08, 2.17868429e-09,
       2.78315545e-08, 2.15363177e-09, 2.56779227e-08, 2.11939853e-09,
       2.35585242e-08, 2.08900149e-09, 2.14695227e-08, 2.07825797e-09,
       1.93912647e-08, 2.06644002e-09, 1.73248247e-08, 2.01978713e-09,
       1.53050376e-08, 1.99198793e-09, 1.33130497e-08, 1.97650517e-09,
       1.13365445e-08, 1.95105644e-09, 9.38548805e-09, 1.93388972e-09,
       7.45159834e-09, 1.89252276e-09, 5.55907558e-09, 1.88291733e-09,
       3.67615824e-09, 1.84670668e-09, 1.82945156e-09])
In [11]:
#Количество выполненных приближений
sol.nfev
Out[11]:
667

Проверка: выражения должны возвращать значение напора насоса

In [12]:
#Ближайший потребитель i = 0
(Rs[0] + Rs[2]) * sol.x[0] ** 2 + Rs[1] * sol.x[1] ** 2
Out[12]:
1000.0
In [13]:
#Самый дальний потребитель i = N-1
dH = 0
for i in range(N):
    dH += (Rs[3*i] + Rs[3*i+2]) * sol.x[2*i] ** 2
dH + Rs[-2] * sol.x[-1] ** 2
Out[13]:
1000.0000000000001

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

In [14]:
root(func,np.ones(2*N-1)).x
Out[14]:
array([ 2.85935083e+01,  1.62095851e+01,  1.23839232e+01,  8.23417608e+00,
        4.14974711e+00,  2.33190642e+00,  1.81784069e+00,  1.20293802e+00,
        6.14902668e-01,  3.73733993e-01,  2.41168675e-01,  2.08645815e-01,
        3.25228602e-02,  1.73955191e-02,  1.51273411e-02,  1.05805562e-02,
        4.54678486e-03,  2.87054677e-03,  1.67623808e-03,  9.56473355e-04,
        7.19764727e-04,  3.97977279e-04,  3.21787448e-04,  2.07539122e-04,
        1.14248326e-04,  6.33218798e-05,  5.09264457e-05,  3.52486472e-05,
        1.56777986e-05,  6.76580305e-06,  8.91199551e-06,  5.47631888e-06,
        3.43567663e-06,  2.67503842e-06,  7.60638210e-07,  4.93993331e-07,
        2.66644879e-07,  1.79368897e-07,  8.72759821e-08,  2.51936727e-08,
        6.20823094e-08, -2.47638889e-08,  8.68461982e-08,  6.59687705e-09,
        8.02493212e-08, -1.43562906e-08,  9.46056118e-08,  1.76876679e-08,
        7.69179440e-08,  3.16166179e-08,  4.53013260e-08, -4.91501212e-08,
        9.44514472e-08, -1.47477178e-08,  1.09199165e-07,  3.36335629e-08,
        7.55656021e-08,  3.71316125e-08,  3.84339896e-08, -6.14181585e-08,
        9.98521481e-08,  2.55886018e-08,  7.42635463e-08, -5.88096016e-09,
        8.01445064e-08,  8.20486620e-09,  7.19396402e-08,  2.91600013e-08,
        4.27796390e-08, -5.07332293e-08,  9.35128683e-08,  1.61904725e-08,
        7.73223958e-08, -4.08988975e-09,  8.14122855e-08, -2.82231080e-08,
        1.09635394e-07,  3.70124387e-08,  7.26229549e-08,  6.65257516e-09,
        6.59703797e-08, -2.26383129e-08,  8.86086926e-08,  5.06723673e-09,
        8.35414559e-08,  1.38784840e-08,  6.96629719e-08, -7.48376531e-09,
        7.71467372e-08,  6.00621430e-09,  7.11405229e-08, -2.32912765e-08,
        9.44317993e-08,  3.45968838e-08,  5.98349155e-08, -4.32266336e-08,
        1.03061549e-07,  3.99397579e-08,  6.31217912e-08])