|
1 | 1 | """ |
2 | | -Module to analyze homogeneous linear differential equations. |
| 2 | +wronskian_second_order_de.py |
| 3 | +A symbolic and numerical exploration of the Wronskian for second-order linear differential equations. |
3 | 4 |
|
4 | | -It supports: |
5 | | - - Second-order equations: a*y'' + b*y' + c*y = 0 |
6 | | - - First-order equations: b*y' + c*y = 0 (when a = 0) |
| 5 | +This program: |
| 6 | +1. Takes coefficients (a, b, c) for a*y'' + b*y' + c*y = 0. |
| 7 | +2. Computes characteristic roots. |
| 8 | +3. Classifies the solution type. |
| 9 | +4. Constructs the general solution. |
| 10 | +5. Demonstrates Wronskian computation. |
7 | 11 |
|
8 | | -Features: |
9 | | - - Computes characteristic roots (for second-order) |
10 | | - - Derives fundamental solutions |
11 | | - - Calculates first derivatives |
12 | | - - Evaluates the Wronskian determinant |
13 | | - - Tests for linear independence |
14 | | -
|
15 | | -References: |
16 | | - https://en.wikipedia.org/wiki/Linear_differential_equation |
| 12 | +Author: Venkat Thadi |
17 | 13 | """ |
18 | 14 |
|
| 15 | +import math |
19 | 16 | import cmath |
20 | | -from sympy import symbols, exp, cos, sin, diff, simplify |
21 | | - |
22 | | - |
23 | | -def compute_characteristic_roots( |
24 | | - coefficient_a: float, coefficient_b: float, coefficient_c: float |
25 | | -) -> tuple[complex, complex]: |
26 | | - """ |
27 | | - Compute roots of the characteristic equation: |
28 | | - a*r^2 + b*r + c = 0 |
29 | 17 |
|
30 | | - >>> compute_characteristic_roots(1, -2, 1) |
31 | | - ((1+0j), (1+0j)) |
32 | | - >>> compute_characteristic_roots(1, 0, 1) |
33 | | - ((0+1j), (0-1j)) |
| 18 | +def compute_characteristic_roots(a: float, b: float, c: float) -> tuple[complex, complex]: |
34 | 19 | """ |
35 | | - discriminant = coefficient_b**2 - 4 * coefficient_a * coefficient_c |
36 | | - sqrt_discriminant = cmath.sqrt(discriminant) |
37 | | - root_1 = (-coefficient_b + sqrt_discriminant) / (2 * coefficient_a) |
38 | | - root_2 = (-coefficient_b - sqrt_discriminant) / (2 * coefficient_a) |
39 | | - return root_1, root_2 |
| 20 | + Compute characteristic roots for a second-order homogeneous linear DE. |
40 | 21 |
|
41 | | - |
42 | | -def construct_fundamental_solutions(root_1: complex, root_2: complex): |
43 | | - """ |
44 | | - Construct fundamental solutions (y1, y2) of a 2nd-order ODE. |
45 | | -
|
46 | | - >>> from sympy import symbols, exp |
47 | | - >>> x = symbols('x') |
48 | | - >>> r1, r2 = 1, 1 |
49 | | - >>> construct_fundamental_solutions(r1, r2) |
50 | | - (exp(x), x*exp(x)) |
| 22 | + >>> compute_characteristic_roots(1, -3, 2) |
| 23 | + (2.0, 1.0) |
| 24 | + >>> compute_characteristic_roots(1, 2, 5) |
| 25 | + ((-1+2j), (-1-2j)) |
51 | 26 | """ |
52 | | - variable_x = symbols("x") |
| 27 | + if a == 0: |
| 28 | + raise ValueError("Coefficient 'a' cannot be zero for a second-order equation.") |
53 | 29 |
|
54 | | - # Case 1: Real and equal roots |
55 | | - if root_1 == root_2 and root_1.imag == 0: |
56 | | - solution_1 = exp(root_1.real * variable_x) |
57 | | - solution_2 = variable_x * exp(root_1.real * variable_x) |
| 30 | + discriminant = b ** 2 - 4 * a * c |
| 31 | + sqrt_disc = cmath.sqrt(discriminant) |
| 32 | + root1 = (-b + sqrt_disc) / (2 * a) |
| 33 | + root2 = (-b - sqrt_disc) / (2 * a) |
58 | 34 |
|
59 | | - # Case 2: Real and distinct roots |
60 | | - elif root_1.imag == 0 and root_2.imag == 0: |
61 | | - solution_1 = exp(root_1.real * variable_x) |
62 | | - solution_2 = exp(root_2.real * variable_x) |
| 35 | + # Simplify if roots are purely real |
| 36 | + if abs(root1.imag) < 1e-12: |
| 37 | + root1 = float(root1.real) |
| 38 | + if abs(root2.imag) < 1e-12: |
| 39 | + root2 = float(root2.real) |
63 | 40 |
|
64 | | - # Case 3: Complex conjugate roots (α ± iβ) |
65 | | - else: |
66 | | - alpha = root_1.real |
67 | | - beta = abs(root_1.imag) |
68 | | - solution_1 = exp(alpha * variable_x) * cos(beta * variable_x) |
69 | | - solution_2 = exp(alpha * variable_x) * sin(beta * variable_x) |
| 41 | + return root1, root2 |
70 | 42 |
|
71 | | - return solution_1, solution_2 |
72 | 43 |
|
73 | | - |
74 | | -def compute_wronskian(function_1, function_2): |
| 44 | +def classify_solution_type(root1: complex, root2: complex) -> str: |
75 | 45 | """ |
76 | | - Compute the Wronskian determinant of two functions. |
77 | | -
|
78 | | - >>> from sympy import symbols, exp |
79 | | - >>> x = symbols('x') |
80 | | - >>> f1, f2 = exp(x), x*exp(x) |
81 | | - >>> compute_wronskian(f1, f2) |
82 | | - exp(2*x) |
| 46 | + Classify the nature of the roots. |
| 47 | +
|
| 48 | + >>> classify_solution_type(2, 1) |
| 49 | + 'Distinct Real Roots' |
| 50 | + >>> classify_solution_type(1+2j, 1-2j) |
| 51 | + 'Complex Conjugate Roots' |
| 52 | + >>> classify_solution_type(3, 3) |
| 53 | + 'Repeated Real Roots' |
83 | 54 | """ |
84 | | - variable_x = symbols("x") |
85 | | - derivative_1 = diff(function_1, variable_x) |
86 | | - derivative_2 = diff(function_2, variable_x) |
87 | | - wronskian = simplify(function_1 * derivative_2 - function_2 * derivative_1) |
88 | | - return wronskian |
| 55 | + if isinstance(root1, complex) and isinstance(root2, complex) and root1.imag != 0: |
| 56 | + return "Complex Conjugate Roots" |
| 57 | + elif root1 == root2: |
| 58 | + return "Repeated Real Roots" |
| 59 | + else: |
| 60 | + return "Distinct Real Roots" |
89 | 61 |
|
90 | 62 |
|
91 | | -def solve_first_order_equation(coefficient_b: float, coefficient_c: float) -> None: |
| 63 | +def compute_wronskian(f, g, f_prime, g_prime, x: float) -> float: |
92 | 64 | """ |
93 | | - Solve the first-order ODE: b*y' + c*y = 0 |
94 | | - and display its general solution and Wronskian. |
95 | | -
|
96 | | - >>> solve_first_order_equation(2, 4) |
| 65 | + Compute Wronskian determinant W(f, g) = f * g' - f' * g. |
| 66 | +
|
| 67 | + >>> import math |
| 68 | + >>> def f(x): return math.exp(x) |
| 69 | + >>> def g(x): return x * math.exp(x) |
| 70 | + >>> def f_prime(x): return math.exp(x) |
| 71 | + >>> def g_prime(x): return math.exp(x) + x * math.exp(x) |
| 72 | + >>> round(compute_wronskian(f, g, f_prime, g_prime, 0), 3) |
| 73 | + 1.0 |
97 | 74 | """ |
98 | | - variable_x = symbols("x") |
99 | | - if coefficient_b == 0: |
100 | | - print("Error: Both a and b cannot be zero. Not a valid differential equation.") |
101 | | - return |
102 | | - |
103 | | - # Simplified form: y' + (c/b)*y = 0 |
104 | | - constant_k = coefficient_c / coefficient_b |
105 | | - solution = exp(-constant_k * variable_x) |
106 | | - |
107 | | - derivative_solution = diff(solution, variable_x) |
108 | | - wronskian = simplify(solution * derivative_solution) |
| 75 | + return f(x) * g_prime(x) - f_prime(x) * g(x) |
109 | 76 |
|
110 | | - print("\n--- First-Order Differential Equation ---") |
111 | | - print(f"Equation: {coefficient_b}*y' + {coefficient_c}*y = 0") |
112 | | - print(f"Solution: y = C * e^(-({coefficient_c}/{coefficient_b}) * x)") |
113 | | - print(f"y'(x) = {derivative_solution}") |
114 | | - print(f"Wronskian (single function): {wronskian}") |
115 | | - print("Linear independence: Trivial (only one solution).") |
116 | 77 |
|
117 | | - |
118 | | -def analyze_differential_equation( |
119 | | - coefficient_a: float, coefficient_b: float, coefficient_c: float |
120 | | -) -> None: |
| 78 | +def construct_general_solution(root1: complex, root2: complex) -> str: |
121 | 79 | """ |
122 | | - Determine the type of equation and analyze it accordingly. |
| 80 | + Construct the general solution based on the roots. |
| 81 | +
|
| 82 | + >>> construct_general_solution(2, 1) |
| 83 | + 'y(x) = C1 * e^(2x) + C2 * e^(1x)' |
| 84 | + >>> construct_general_solution(3, 3) |
| 85 | + 'y(x) = (C1 + C2 * x) * e^(3x)' |
| 86 | + >>> construct_general_solution(-1+2j, -1-2j) |
| 87 | + 'y(x) = e^(-1x) * (C1 * cos(2x) + C2 * sin(2x))' |
123 | 88 | """ |
124 | | - # Case 1: Not a valid DE |
125 | | - if coefficient_a == 0 and coefficient_b == 0: |
126 | | - print( |
127 | | - "Error: Both 'a' and 'b' cannot be zero. Not a valid differential equation." |
128 | | - ) |
129 | | - return |
130 | | - |
131 | | - # Case 2: First-order DE |
132 | | - if coefficient_a == 0: |
133 | | - solve_first_order_equation(coefficient_b, coefficient_c) |
134 | | - return |
135 | | - |
136 | | - # Case 3: Second-order DE |
137 | | - print("\n--- Second-Order Differential Equation ---") |
138 | | - root_1, root_2 = compute_characteristic_roots( |
139 | | - coefficient_a, coefficient_b, coefficient_c |
140 | | - ) |
141 | | - |
142 | | - print(f"Characteristic roots: r1 = {root_1}, r2 = {root_2}") |
| 89 | + if isinstance(root1, complex) and root1.imag != 0: |
| 90 | + alpha = round(root1.real, 10) |
| 91 | + beta = round(abs(root1.imag), 10) |
| 92 | + return f"y(x) = e^({alpha:g}x) * (C1 * cos({beta:g}x) + C2 * sin({beta:g}x))" |
| 93 | + elif root1 == root2: |
| 94 | + return f"y(x) = (C1 + C2 * x) * e^({root1:g}x)" |
| 95 | + else: |
| 96 | + return f"y(x) = C1 * e^({root1:g}x) + C2 * e^({root2:g}x)" |
143 | 97 |
|
144 | | - function_1, function_2 = construct_fundamental_solutions(root_1, root_2) |
145 | 98 |
|
146 | | - variable_x = symbols("x") |
147 | | - derivative_1 = diff(function_1, variable_x) |
148 | | - derivative_2 = diff(function_2, variable_x) |
149 | | - wronskian = compute_wronskian(function_1, function_2) |
| 99 | +def analyze_differential_equation(a: float, b: float, c: float) -> None: |
| 100 | + """ |
| 101 | + Analyze the DE and print the roots, type, and general solution. |
150 | 102 |
|
151 | | - print(f"y₁(x) = {function_1}") |
152 | | - print(f"y₂(x) = {function_2}") |
153 | | - print(f"y₁'(x) = {derivative_1}") |
154 | | - print(f"y₂'(x) = {derivative_2}") |
155 | | - print(f"Wronskian: {wronskian}") |
| 103 | + >>> analyze_differential_equation(1, -3, 2) # doctest: +ELLIPSIS |
| 104 | + Characteristic Roots: (2.0, 1.0) |
| 105 | + Solution Type: Distinct Real Roots |
| 106 | + General Solution: y(x) = C1 * e^(2x) + C2 * e^(1x) |
| 107 | + """ |
| 108 | + roots = compute_characteristic_roots(a, b, c) |
| 109 | + root1, root2 = roots |
| 110 | + sol_type = classify_solution_type(root1, root2) |
| 111 | + general_solution = construct_general_solution(root1, root2) |
156 | 112 |
|
157 | | - if wronskian == 0: |
158 | | - print("The functions are linearly dependent.") |
159 | | - else: |
160 | | - print("The functions are linearly independent.") |
| 113 | + print(f"Characteristic Roots: ({root1:.1f}, {root2:.1f})") |
| 114 | + print(f"Solution Type: {sol_type}") |
| 115 | + print(f"General Solution: {general_solution}") |
161 | 116 |
|
162 | 117 |
|
163 | 118 | def main() -> None: |
164 | 119 | """ |
165 | | - Entry point of the program. |
| 120 | + Main function to run the second-order differential equation Wronskian analysis. |
| 121 | +
|
| 122 | + Interactive input is expected, so this function is skipped in doctests. |
166 | 123 | """ |
167 | 124 | print("Enter coefficients for the equation a*y'' + b*y' + c*y = 0") |
168 | | - try: |
169 | | - coefficient_a = float(input("a = ").strip()) |
170 | | - coefficient_b = float(input("b = ").strip()) |
171 | | - coefficient_c = float(input("c = ").strip()) |
172 | | - except ValueError: |
173 | | - print("Invalid input. Please enter numeric values for coefficients.") |
| 125 | + |
| 126 | + # Skipping main in doctests because input() cannot be tested directly |
| 127 | + a = float(input("a = ").strip()) # doctest: +SKIP |
| 128 | + b = float(input("b = ").strip()) # doctest: +SKIP |
| 129 | + c = float(input("c = ").strip()) # doctest: +SKIP |
| 130 | + |
| 131 | + if a == 0: |
| 132 | + print("Invalid input: coefficient 'a' cannot be zero.") |
174 | 133 | return |
175 | 134 |
|
176 | | - analyze_differential_equation(coefficient_a, coefficient_b, coefficient_c) |
| 135 | + analyze_differential_equation(a, b, c) |
| 136 | + |
177 | 137 |
|
178 | 138 |
|
179 | 139 | if __name__ == "__main__": |
180 | | - main() |
| 140 | + main() # doctest: +SKIP |
0 commit comments