sympy / sympy

A computer algebra system written in pure Python

Home Page:https://sympy.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Solution from dsolve_system violates all equations

OliverAh opened this issue · comments

I noticed, solving a system of ordinary differential equations (ODEs), the solution violates all equations.
This system of equations, in combination with the chosen initial conditions, has a simple solution to compare to, as shown below.

It seems the correct solution is obtained partially, but then something happens and some equations are ignored.
Any suggestions how to resolve this issue is highly appreciated. Thanks!

Variables and equations (feel free to skip to Code).

The 4 equations eq_{1-4}, are obtained by the aim to determine electric currents I_C, I_L and voltages U_C, U_L
as functions of time t in a simple LC-circuit [1], consisting of one capacitor *_C and one inductor *_L only,
based on Kirchhoff's current and voltage laws, also referred to as Kirchhoff's circuit laws [2].
[1] - https://en.wikipedia.org/wiki/LC_circuit#Time_domain_solution
[2] - https://en.wikipedia.org/wiki/Kirchhoff%27s_circuit_laws)

Code:

import sympy

# Define symbols. t is time, I_* and U_* are functions of time and represent
# physical current and voltage at the capacitor (*_C) and the inductor (*_L).
t = sympy.symbols('t', nonnegative=True)
I_C, I_L, U_C, U_L = sympy.symbols('I_C I_L U_C U_L', cls=sympy.Function)

# Define equations. 
eq_1 = sympy.Eq(I_C(t) - I_L(t), 0) # Current law
eq_2 = sympy.Eq(U_C(t) + U_L(t), 0) # Voltage law
eq_3 = sympy.Eq(U_C(t).diff(t,1), I_C(t)) # Capacitor
eq_4 = sympy.Eq(U_L(t), I_L(t).diff(t,1)) # Inductor

# Collect in lists: equations, functions to solve for, and initial conditions.
# Use dsolve_system to solve system of equations.
eqs = [eq_1, eq_2, eq_3, eq_4]
funcs = [I_C(t), I_L(t), U_C(t), U_L(t)]
ics = {I_C(0):0, I_L(0):0, U_C(0):1, U_L(0):-1}
sol = sympy.solvers.ode.systems.dsolve_system(eqs=eqs, funcs=funcs, ics=ics, t=t)

# Print obtained solutions.
for i in range(len(sol)):
    print(f'Solution {i+1} of {len(sol)}')
    for j in range(len(sol[i])):
        print(' |', sol[i][j])

Produced output:

Solution 1 of 1
 | Eq(I_C(t), -sin(t)/2 + cos(t)/2 - exp(-t)/2)
 | Eq(I_L(t), -sin(t))
 | Eq(U_C(t), cos(t))
 | Eq(U_L(t), -sin(t)/2 - cos(t)/2 - exp(-t)/2)

Expected output:

Solution 1 of 1
 | Eq(I_C(t), -sin(t)
 | Eq(I_L(t), -sin(t))
 | Eq(U_C(t), cos(t))
 | Eq(U_L(t), -cos(t))

The correct solution would be
I_L(t) = I_C(t) = -sin(t)
U_C(t) = cos(t)
U_L(t) = -cos(t)

As we can see, the produced output is partially related to the correct solution. Nevertheless, all equations are violated.

Python version: sys.version --> 3.12.3
Sympy version: sympy.__version__ --> 1.12

This is a system of differential algebraic equations (DAEs) rather than ODEs: the first two equations are not differential equations. Ideally this should give an error but you are using the internal dsolve_system function which does not check for this input. If you called the public dsolve function you would get an error.

If you solve the first two equations algebraically to eliminate the additional functions you get:

In [13]: solve(eqs[:2], [U_C(t), I_L(t)])
Out[13]: {I_L(t): I_C(t), U_C(t): -U_L(t)}

In [14]: rep = solve(eqs[:2], [U_C(t), I_L(t)])

In [15]: eqs2 = [eq.subs(rep).doit() for eq in eqs[2:]]

In [16]: eqs2
Out[16]:
⎡ d                             d         ⎤
⎢-──(U_L(t)) = I_C(t), U_L(t) = ──(I_C(t))⎥
⎣ dt                            dtIn [17]: dsolve(eqs2)
Out[17]: [U_L(t) = -C₁⋅sin(t) - C₂⋅cos(t), I_C(t) = C₁⋅cos(t) - C₂⋅sin(t)]

In [19]: dsolve(eqs2, ics={U_L(0): -1, I_C(0): 0})
Out[19]: [U_L(t) = -cos(t), I_C(t) = -sin(t)]

The PR gh-24848 would be able to solve the original system:

In [1]: import sympy
   ...:
   ...: # Define symbols. t is time, I_* and U_* are functions of time and represent
   ...: # physical current and voltage at the capacitor (*_C) and the inductor (*_L).
   ...: t = sympy.symbols('t', nonnegative=True)
   ...: I_C, I_L, U_C, U_L = sympy.symbols('I_C I_L U_C U_L', cls=sympy.Function)
   ...:
   ...: # Define equations.
   ...: eq_1 = sympy.Eq(I_C(t) - I_L(t), 0) # Current law
   ...: eq_2 = sympy.Eq(U_C(t) + U_L(t), 0) # Voltage law
   ...: eq_3 = sympy.Eq(U_C(t).diff(t,1), I_C(t)) # Capacitor
   ...: eq_4 = sympy.Eq(U_L(t), I_L(t).diff(t,1)) # Inductor
   ...:
   ...: # Collect in lists: equations, functions to solve for, and initial conditions.
   ...: # Use dsolve_system to solve system of equations.
   ...: eqs = [eq_1, eq_2, eq_3, eq_4]
   ...: funcs = [I_C(t), I_L(t), U_C(t), U_L(t)]
   ...: ics = {I_C(0):0, I_L(0):0, U_C(0):1, U_L(0):-1}
   ...: sol = sympy.solvers.ode.systems.dsolve_system(eqs=eqs, funcs=funcs, ics=ics, t=t)
   ...:
   ...: # Print obtained solutions.
   ...: for i in range(len(sol)):
   ...:     print(f'Solution {i+1} of {len(sol)}')
   ...:     for j in range(len(sol[i])):
   ...:         print(' |', sol[i][j])
   ...:
Solution 1 of 1
 | Eq(I_C(t), -sin(t))
 | Eq(I_L(t), -sin(t))
 | Eq(U_C(t), cos(t))
 | Eq(U_L(t), -cos(t))

Indeed I just assumed dsolve_system would do, what you suggested with the combination of solve and dsolve.

Is there any plan to go further with the PR you mentioned? As you might have guessed already, I am not very familiar with sympy, but I would be happy to help within the range of my possibilities.

On the long run, I would like to solve more complex systems of equations, and not go in and manually eliminate equations by inspection.