Przejdź do treści
Strona główna » Blog » Układ równań algebraicznych-różniczkowych w Pythonie

Układ równań algebraicznych-różniczkowych w Pythonie

Równania algebraiczne różniczkowe (DAE – Differential-Algebraic Equations) to równania zawierające nieznaną funkcję i jej pochodne. Układ równań algebraicznych-różniczkowych w swojej ogólnej formie opisany jest następująco:

DAE equation

gdzie x(t) to nieznana funkcja i F=F(t, u, v) zawiera N składowych: Fi, i = 1,2,…,N.

Rozważmy przykładowy problem:

System of differential-algebraic equations

Istnieje kilka bibliotek, których można użyć do rozwiązania takich zagadnień, ale ich użycie może być skomplikowane. Tym razem zobaczymy jak rozwiązać taki problem za pomocą fsolve z biblioteki SciPy.

Metoda polega na dyskretyzacji pochodnych czasowych – zamiast dUi/dt użyjemy ΔUiti:

Discretization of DAE

Wartość ΔUi jest równa różnicy między dwoma kolejnymi wartościami czasu. Zatem otrzymujemy:

Discretization of DAE - finite differences

W ten sposób układ równań różniczkowo-algebraicznych został przekształcony w układ równań algebraicznych. Teraz musimy przenieść niezerowe elementy na lewą stronę równania w następujący sposób:

Final form of DAE

Ostatecznie w każdym kroku czasowym musimy rozwiązać układ równań algebraicznych f0, f1,…f7.

Rozwiązyanie układu równań algebraicznych-różniczkowych w Pythonie

Najpierw zaimportujemy niezbędne biblioteki:

import numpy as np
from scipy.optimize import fsolve
import matplotlib.pyplot as plt

Zdefiniujemy funkcję opisującą równania algebraiczne:

def dae_eqs(U, *args):

    # U denotes vector of variables at time t + time_step. U(0) means U1, U(1) is U2 and so on.

    # *args contains Ut and time_step
        # Ut denotes vector of variables at time t.
        # value of time step is necessary for calculating discretized derivatives.

    # Unpack arguments
    Ut, time_step = args
    # set some arbitray values of parameters
    c1 = 1.0
    c2 = 2.0
    c3 = 3.0
    c4 = 4.0
    R2 = 2.0
    R3 = 3.0
    R4 = 4.0
    B1 = 1.0
    B2 = 2.0
    E = 5.0

    # Let's assign values of U array to U1, U2, ... for ease.
    U1 = U[0]
    U2 = U[1]
    U3 = U[2]
    U4 = U[3]
    U5 = U[4]
    U6 = U[5]
    U7 = U[6]
    U8 = U[7]
    # The same for array Ut.
    Ut1 = Ut[0]
    Ut2 = Ut[1]
    Ut3 = Ut[2]
    Ut4 = Ut[3]
    Ut5 = Ut[4]
    Ut6 = Ut[5]
    Ut7 = Ut[6]
    Ut8 = Ut[7]

    # Derivatives of U valuses. Some of them are not used, but let's leave them for consistency.
    U1_deriv = (U1-Ut1)/time_step
    U2_deriv = (U2-Ut2)/time_step
    U3_deriv = (U3-Ut3)/time_step
    U4_deriv = (U4-Ut4)/time_step 
    U5_deriv = (U5-Ut5)/time_step
    U6_deriv = (U6-Ut6)/time_step
    U7_deriv = (U7-Ut7)/time_step   
    U8_deriv = (U8-Ut8)/time_step   

    # calculate values of algebraic equations
    f0 = c1 * U3_deriv + c3 * U7_deriv - E
    f1 = c1 * U3_deriv - U4/R3
    f2 = c2 * U5_deriv + c4 * U8_deriv - E
    f3 = c2 * U5_deriv - U6/R4
    f4 = B1 + U7 - U4 - U3
    f5 = U6 + U5 - U8 - B2
    f6 = U7 + U8 - E*R2 - U1
    f7 = B1 - U8 - E*U2 - U1 - U3 - U4

    # Gather all fs into one array
    f = np.array([f0, f1, f2, f3, f4, f5, f6, f7])
    return f

Zgodnie z wyjaśnieniem w pierwszej części artykułu, program główny wygląda następująco:

# set initial conditions for t = 0
number_of_unknowns = 8
U = np.zeros(number_of_unknowns)

# If initial conditions are different from zero, they should be set separately, as below
U[0] = 1.0
U[1] = 1.0
U[2] = 1.0
U[3] = 1.0
U[4] = 1.0
U[5] = 1.0
U[6] = 1.0    
U[7] = 1.0   

# set time step, end_time and time
time_step = 0.1
end_time = 5
time = 0

# Initial condition must satisfy the system. Therefore it is required to find proper solution at time 0.
u_0 = fsolve(dae_eqs, U, args=(U,time_step))

# Create arrays for storing solution
time_solution = np.linspace(0, end_time, (int)(end_time/time_step)+1)
u_solution = np.zeros((time_solution.size, number_of_unknowns))
i = 0
u_solution[0] = u_0 # assign initial condition
while time < end_time - time_step:
    args = (U, time_step) # arguments for fsolve
    u_next =  fsolve(dae_eqs, U, args=args) # calculate u at time t + time_step
    U = u_next # in next iteration U is equal U from previous iteration 
    u_solution[i+1] = u_next
    time += time_step # increase time
    i += 1

Teraz możemy narysować wykres przedstawiający wyniki.

fig, ax = plt.subplots(2)
ax[0].plot(time_solution, u_solution[:,0])

ax[0].set(ylabel='U1')
ax[0].grid()

ax[1].plot(time_solution, u_solution[:,1])

ax[1].set(xlabel='time (s)', ylabel='U2')
ax[1].grid()

#Plot the solution of differential-algebraic equations
plt.show()
Result visualization

Podsumowanie

Powyżej widzieliśmy, jak rozwiązywać równania algebraiczne różniczkowe w Pythonie. Takie problemy pojawiają się w wielu dziedzinach inżynierii, w tym biochemii, elektrotechnice i inżynierii procesowej.

Powyższa metoda nie jest doskonała. Wykorzystuje najprostszą dyskretyzację i nie zawsze będzie działać, na przykład, jeśli mamy do czynienia ze sztywnymi równaniami różniczkowymi. Istnieje kilka bibliotek Pythona, których można użyć. Niektóre z nich są wymienione poniżej:

https://pypi.org/project/diffeqpy/

https://lcvmwww.epfl.ch/software/daepy/

Więcej na temat DAE możesz dowiedzieć się na stronie Scholarpedia.

Jeśli chcesz dowiedzieć się więcej o rozwiązywaniu problemów inżynierskich za pomocą Pythona, zapoznaj się z ofertą kursu: Wprowadzenie do obliczeń naukowych przy użyciu języka Python

Skontaktuj się z nami

Skontaktuj się z nami, aby uzyskać profesjonalną pomoc w rozwiązywaniu problemów związanych z symulacjami i modelowaniem matematycznym.