Upload 22 files
Browse files- .gitattributes +5 -0
- Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.24 PM.png +3 -0
- Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.37 PM.png +3 -0
- Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.50 PM.png +3 -0
- Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.50.16 PM.png +3 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_000.py +247 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_001.py +318 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_002.py +326 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_003.py +374 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_004.py +405 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_005.py +400 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_006.py +412 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_007.py +421 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_008.py +451 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_009.py +424 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_final.py +436 -0
- Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_optimized_final.py +436 -0
- Case Study Constructing Gradient Fields/requirements.txt +1 -0
- Case Study Constructing Gradient Fields/test_corrected_examples.py +43 -0
- app.py +507 -0
- constructing_gradient_fields_case_1_and_2_optimized_final.py +436 -0
- output.mp4 +3 -0
- requirements.txt +1 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
Case[[:space:]]Study[[:space:]]Constructing[[:space:]]Gradient[[:space:]]Fields/Screenshot[[:space:]]2025-11-20[[:space:]]at[[:space:]]12.49.24 PM.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
Case[[:space:]]Study[[:space:]]Constructing[[:space:]]Gradient[[:space:]]Fields/Screenshot[[:space:]]2025-11-20[[:space:]]at[[:space:]]12.49.37 PM.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
Case[[:space:]]Study[[:space:]]Constructing[[:space:]]Gradient[[:space:]]Fields/Screenshot[[:space:]]2025-11-20[[:space:]]at[[:space:]]12.49.50 PM.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
Case[[:space:]]Study[[:space:]]Constructing[[:space:]]Gradient[[:space:]]Fields/Screenshot[[:space:]]2025-11-20[[:space:]]at[[:space:]]12.50.16 PM.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
output.mp4 filter=lfs diff=lfs merge=lfs -text
|
Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.24 PM.png
ADDED
|
Git LFS Details
|
Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.37 PM.png
ADDED
|
Git LFS Details
|
Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.50 PM.png
ADDED
|
Git LFS Details
|
Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.50.16 PM.png
ADDED
|
Git LFS Details
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_000.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x) - Vx
|
| 23 |
+
grad_y = sp.diff(phi, self.y) - Vy
|
| 24 |
+
|
| 25 |
+
return sp.simplify(grad_x) == 0 and sp.simplify(grad_y) == 0
|
| 26 |
+
|
| 27 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 28 |
+
"""Display the results in a formatted way"""
|
| 29 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 30 |
+
print(f"Potential Function: φ(x,y) = {phi}")
|
| 31 |
+
print(f"Verification: ∇φ = F? {self.verify_potential(phi, Vx, Vy)}")
|
| 32 |
+
print("-" * 50)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 36 |
+
"""Case 1: One component is constant"""
|
| 37 |
+
|
| 38 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 39 |
+
super().__init__()
|
| 40 |
+
self.constant_component = constant_component.lower()
|
| 41 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 42 |
+
|
| 43 |
+
# Define unknown functions
|
| 44 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 45 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 46 |
+
|
| 47 |
+
def find_potential(self) -> sp.Expr:
|
| 48 |
+
if self.constant_component == 'x':
|
| 49 |
+
# F(x,y) = [c, F_y(y)]
|
| 50 |
+
Vx = self.c
|
| 51 |
+
Vy = self.F_y
|
| 52 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 53 |
+
else: # constant y-component
|
| 54 |
+
# F(x,y) = [F_x(x), c]
|
| 55 |
+
Vx = self.F_x
|
| 56 |
+
Vy = self.c
|
| 57 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 58 |
+
|
| 59 |
+
return phi
|
| 60 |
+
|
| 61 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 62 |
+
if self.constant_component == 'x':
|
| 63 |
+
return self.c, self.F_y
|
| 64 |
+
else:
|
| 65 |
+
return self.F_x, self.c
|
| 66 |
+
|
| 67 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 68 |
+
"""Return specific cases with actual functions"""
|
| 69 |
+
cases = {}
|
| 70 |
+
|
| 71 |
+
# Case 1a: Constant x-component with specific F_y
|
| 72 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 73 |
+
Vx1, Vy1 = self.c, F_y_specific
|
| 74 |
+
phi1 = self.c * self.x + sp.Integral(F_y_specific, self.y).doit()
|
| 75 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 76 |
+
|
| 77 |
+
# Case 1b: Constant y-component with specific F_x
|
| 78 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 79 |
+
Vx2, Vy2 = F_x_specific, self.c
|
| 80 |
+
phi2 = self.c * self.y + sp.Integral(F_x_specific, self.x).doit()
|
| 81 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 82 |
+
|
| 83 |
+
return cases
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 87 |
+
"""Case 2: Both components are linear functions"""
|
| 88 |
+
|
| 89 |
+
def __init__(self):
|
| 90 |
+
super().__init__()
|
| 91 |
+
# Define constants for linear functions
|
| 92 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 93 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 94 |
+
|
| 95 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 96 |
+
"""Find potential function for given linear vector field"""
|
| 97 |
+
if Vx is None or Vy is None:
|
| 98 |
+
Vx, Vy = self.get_general_linear_field()
|
| 99 |
+
|
| 100 |
+
# Use the method from the case study
|
| 101 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 102 |
+
return phi
|
| 103 |
+
|
| 104 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 105 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 106 |
+
# Integrate Vx with respect to x
|
| 107 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 108 |
+
|
| 109 |
+
# Differentiate with respect to y and compare with Vy
|
| 110 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 111 |
+
remaining = Vy - diff_phi_x_y
|
| 112 |
+
|
| 113 |
+
# Integrate remaining with respect to y
|
| 114 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 115 |
+
|
| 116 |
+
# Combine results
|
| 117 |
+
phi = phi_x + phi_y
|
| 118 |
+
|
| 119 |
+
return phi
|
| 120 |
+
|
| 121 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 122 |
+
return self.get_general_linear_field()
|
| 123 |
+
|
| 124 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 125 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 126 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 127 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 128 |
+
return Vx, Vy
|
| 129 |
+
|
| 130 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 131 |
+
"""Return the condition for the field to be a gradient field"""
|
| 132 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 133 |
+
Vx, Vy = self.get_general_linear_field()
|
| 134 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 135 |
+
return sp.simplify(condition)
|
| 136 |
+
|
| 137 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 138 |
+
"""Return specific cases where the field is a gradient field"""
|
| 139 |
+
cases = {}
|
| 140 |
+
|
| 141 |
+
# Case 2a: a2 = b1 (from the case study)
|
| 142 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 143 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 144 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 145 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 146 |
+
|
| 147 |
+
# Case 2b: b2 = a1 (alternative symmetric case)
|
| 148 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 149 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 150 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 151 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 152 |
+
|
| 153 |
+
return cases
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
class GradientFieldFactory:
|
| 157 |
+
"""Factory class to create different types of gradient fields"""
|
| 158 |
+
|
| 159 |
+
@staticmethod
|
| 160 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 161 |
+
return ConstantComponentField(component, constant)
|
| 162 |
+
|
| 163 |
+
@staticmethod
|
| 164 |
+
def create_linear_component_field():
|
| 165 |
+
return LinearComponentField()
|
| 166 |
+
|
| 167 |
+
@staticmethod
|
| 168 |
+
def analyze_all_cases():
|
| 169 |
+
"""Analyze all cases from the case study"""
|
| 170 |
+
print("=" * 60)
|
| 171 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 172 |
+
print("=" * 60)
|
| 173 |
+
|
| 174 |
+
# Case 1: Constant component
|
| 175 |
+
print("\nCASE 1: One Component is Constant")
|
| 176 |
+
print("-" * 40)
|
| 177 |
+
|
| 178 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 179 |
+
phi1 = case1_x.find_potential()
|
| 180 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 181 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 182 |
+
|
| 183 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 184 |
+
phi2 = case1_y.find_potential()
|
| 185 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 186 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 187 |
+
|
| 188 |
+
# Show specific examples
|
| 189 |
+
print("\nSpecific Examples for Case 1:")
|
| 190 |
+
examples1 = case1_x.get_specific_cases()
|
| 191 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 192 |
+
print(f"{case_name}:")
|
| 193 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 194 |
+
|
| 195 |
+
# Case 2: Linear components
|
| 196 |
+
print("\nCASE 2: Both Components are Linear")
|
| 197 |
+
print("-" * 40)
|
| 198 |
+
|
| 199 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 200 |
+
|
| 201 |
+
# Show gradient condition
|
| 202 |
+
condition = case2.get_gradient_condition()
|
| 203 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 204 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 205 |
+
|
| 206 |
+
# Show specific gradient cases
|
| 207 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 208 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 209 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 210 |
+
print(f"{case_name}:")
|
| 211 |
+
case2.display_results(phi, Vx, Vy)
|
| 212 |
+
|
| 213 |
+
return case1_x, case1_y, case2
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# Demonstration and testing
|
| 217 |
+
if __name__ == "__main__":
|
| 218 |
+
# Run the complete analysis
|
| 219 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 220 |
+
|
| 221 |
+
# Additional verification with numerical examples
|
| 222 |
+
print("\n" + "=" * 60)
|
| 223 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 224 |
+
print("=" * 60)
|
| 225 |
+
|
| 226 |
+
# Numerical example for Case 1
|
| 227 |
+
print("\nNumerical Example - Case 1:")
|
| 228 |
+
x, y = sp.symbols('x y')
|
| 229 |
+
c_val = 2
|
| 230 |
+
|
| 231 |
+
# F(x,y) = [2, 3y²]
|
| 232 |
+
Vx_num1 = c_val
|
| 233 |
+
Vy_num1 = 3 * y**2
|
| 234 |
+
phi_num1 = c_val * x + y**3 # ∫3y² dy = y³
|
| 235 |
+
|
| 236 |
+
constant_analyzer = ConstantComponentField('x', sp.symbols('c'))
|
| 237 |
+
constant_analyzer.display_results(phi_num1, Vx_num1, Vy_num1)
|
| 238 |
+
|
| 239 |
+
# Numerical example for Case 2
|
| 240 |
+
print("\nNumerical Example - Case 2:")
|
| 241 |
+
# F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 242 |
+
Vx_num2 = 2*x + 3*y + 1
|
| 243 |
+
Vy_num2 = 3*x + 4*y + 2
|
| 244 |
+
phi_num2 = x**2 + 3*x*y + 2*y**2 + x + 2*y
|
| 245 |
+
|
| 246 |
+
linear_analyzer = LinearComponentField()
|
| 247 |
+
linear_analyzer.display_results(phi_num2, Vx_num2, Vy_num2)
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_001.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
return diff_x == 0 and diff_y == 0, diff_x, diff_y
|
| 29 |
+
|
| 30 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 31 |
+
"""Display the results in a formatted way"""
|
| 32 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 33 |
+
print(f"Potential Function: φ(x,y) = {phi}")
|
| 34 |
+
|
| 35 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 36 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 37 |
+
if not is_valid:
|
| 38 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 39 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 40 |
+
print("-" * 50)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 44 |
+
"""Case 1: One component is constant"""
|
| 45 |
+
|
| 46 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.constant_component = constant_component.lower()
|
| 49 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 50 |
+
|
| 51 |
+
# Define unknown functions
|
| 52 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 53 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 54 |
+
|
| 55 |
+
def find_potential(self) -> sp.Expr:
|
| 56 |
+
if self.constant_component == 'x':
|
| 57 |
+
# F(x,y) = [c, F_y(y)]
|
| 58 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 59 |
+
else: # constant y-component
|
| 60 |
+
# F(x,y) = [F_x(x), c]
|
| 61 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 62 |
+
|
| 63 |
+
return phi
|
| 64 |
+
|
| 65 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 66 |
+
if self.constant_component == 'x':
|
| 67 |
+
return self.c, self.F_y
|
| 68 |
+
else:
|
| 69 |
+
return self.F_x, self.c
|
| 70 |
+
|
| 71 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 72 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 73 |
+
if Vx.is_constant(): # Constant x-component
|
| 74 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 75 |
+
elif Vy.is_constant(): # Constant y-component
|
| 76 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 77 |
+
else:
|
| 78 |
+
raise ValueError("One component must be constant")
|
| 79 |
+
return phi
|
| 80 |
+
|
| 81 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 82 |
+
"""Return specific cases with actual functions"""
|
| 83 |
+
cases = {}
|
| 84 |
+
|
| 85 |
+
# Case 1a: Constant x-component with specific F_y
|
| 86 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 87 |
+
Vx1, Vy1 = self.c, F_y_specific
|
| 88 |
+
phi1 = self.find_potential_for_specific_field(self.c, F_y_specific)
|
| 89 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 90 |
+
|
| 91 |
+
# Case 1b: Constant y-component with specific F_x
|
| 92 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 93 |
+
Vx2, Vy2 = F_x_specific, self.c
|
| 94 |
+
phi2 = self.find_potential_for_specific_field(F_x_specific, self.c)
|
| 95 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 96 |
+
|
| 97 |
+
return cases
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 101 |
+
"""Case 2: Both components are linear functions"""
|
| 102 |
+
|
| 103 |
+
def __init__(self):
|
| 104 |
+
super().__init__()
|
| 105 |
+
# Define constants for linear functions
|
| 106 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 107 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 108 |
+
|
| 109 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 110 |
+
"""Find potential function for given linear vector field"""
|
| 111 |
+
if Vx is None or Vy is None:
|
| 112 |
+
Vx, Vy = self.get_general_linear_field()
|
| 113 |
+
|
| 114 |
+
# Use the method from the case study
|
| 115 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 116 |
+
return phi
|
| 117 |
+
|
| 118 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 119 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 120 |
+
# Method 1: Integrate Vx with respect to x, then determine y-dependent part
|
| 121 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 122 |
+
|
| 123 |
+
# The integration constant might be a function of y
|
| 124 |
+
# Differentiate with respect to y and compare with Vy
|
| 125 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 126 |
+
remaining = Vy - diff_phi_x_y
|
| 127 |
+
|
| 128 |
+
# Integrate remaining with respect to y
|
| 129 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 130 |
+
|
| 131 |
+
# Combine results
|
| 132 |
+
phi = phi_x + phi_y
|
| 133 |
+
|
| 134 |
+
return phi
|
| 135 |
+
|
| 136 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 137 |
+
return self.get_general_linear_field()
|
| 138 |
+
|
| 139 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 140 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 141 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 142 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 143 |
+
return Vx, Vy
|
| 144 |
+
|
| 145 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 146 |
+
"""Return the condition for the field to be a gradient field"""
|
| 147 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 148 |
+
Vx, Vy = self.get_general_linear_field()
|
| 149 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 150 |
+
return sp.simplify(condition)
|
| 151 |
+
|
| 152 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 153 |
+
"""Find potential for a specific numeric vector field"""
|
| 154 |
+
return self._find_potential_2d(Vx, Vy)
|
| 155 |
+
|
| 156 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 157 |
+
"""Return specific cases where the field is a gradient field"""
|
| 158 |
+
cases = {}
|
| 159 |
+
|
| 160 |
+
# Case 2a: a2 = b1 (from the case study)
|
| 161 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 162 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 163 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 164 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 165 |
+
|
| 166 |
+
# Case 2b: b2 = a1 (alternative symmetric case)
|
| 167 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 168 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 169 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 170 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 171 |
+
|
| 172 |
+
return cases
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
class GradientFieldFactory:
|
| 176 |
+
"""Factory class to create different types of gradient fields"""
|
| 177 |
+
|
| 178 |
+
@staticmethod
|
| 179 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 180 |
+
return ConstantComponentField(component, constant)
|
| 181 |
+
|
| 182 |
+
@staticmethod
|
| 183 |
+
def create_linear_component_field():
|
| 184 |
+
return LinearComponentField()
|
| 185 |
+
|
| 186 |
+
@staticmethod
|
| 187 |
+
def analyze_all_cases():
|
| 188 |
+
"""Analyze all cases from the case study"""
|
| 189 |
+
print("=" * 60)
|
| 190 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 191 |
+
print("=" * 60)
|
| 192 |
+
|
| 193 |
+
# Case 1: Constant component
|
| 194 |
+
print("\nCASE 1: One Component is Constant")
|
| 195 |
+
print("-" * 40)
|
| 196 |
+
|
| 197 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 198 |
+
phi1 = case1_x.find_potential()
|
| 199 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 200 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 201 |
+
|
| 202 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 203 |
+
phi2 = case1_y.find_potential()
|
| 204 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 205 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 206 |
+
|
| 207 |
+
# Show specific examples
|
| 208 |
+
print("\nSpecific Examples for Case 1:")
|
| 209 |
+
examples1 = case1_x.get_specific_cases()
|
| 210 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 211 |
+
print(f"{case_name}:")
|
| 212 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 213 |
+
|
| 214 |
+
# Case 2: Linear components
|
| 215 |
+
print("\nCASE 2: Both Components are Linear")
|
| 216 |
+
print("-" * 40)
|
| 217 |
+
|
| 218 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 219 |
+
|
| 220 |
+
# Show gradient condition
|
| 221 |
+
condition = case2.get_gradient_condition()
|
| 222 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 223 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 224 |
+
|
| 225 |
+
# Show specific gradient cases
|
| 226 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 227 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 228 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 229 |
+
print(f"{case_name}:")
|
| 230 |
+
case2.display_results(phi, Vx, Vy)
|
| 231 |
+
|
| 232 |
+
return case1_x, case1_y, case2
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class NumericalExamples:
|
| 236 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 237 |
+
|
| 238 |
+
def __init__(self):
|
| 239 |
+
self.x, self.y = sp.symbols('x y')
|
| 240 |
+
self.constant_analyzer = ConstantComponentField()
|
| 241 |
+
self.linear_analyzer = LinearComponentField()
|
| 242 |
+
|
| 243 |
+
def run_case1_examples(self):
|
| 244 |
+
"""Run numerical examples for Case 1"""
|
| 245 |
+
print("\nNumerical Examples - Case 1:")
|
| 246 |
+
print("-" * 40)
|
| 247 |
+
|
| 248 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 249 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 250 |
+
Vx1 = 2
|
| 251 |
+
Vy1 = 3 * self.y**2
|
| 252 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 253 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 254 |
+
|
| 255 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 256 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 257 |
+
Vx2 = sp.sin(self.x)
|
| 258 |
+
Vy2 = 5
|
| 259 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 260 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 261 |
+
|
| 262 |
+
def run_case2_examples(self):
|
| 263 |
+
"""Run numerical examples for Case 2"""
|
| 264 |
+
print("\nNumerical Examples - Case 2:")
|
| 265 |
+
print("-" * 40)
|
| 266 |
+
|
| 267 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 268 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 269 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 270 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 271 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 272 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 273 |
+
|
| 274 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 275 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 276 |
+
Vx2 = self.x + 2*self.y
|
| 277 |
+
Vy2 = 2*self.x + 3*self.y
|
| 278 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 279 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 280 |
+
|
| 281 |
+
# Example 3: Non-gradient field (should fail verification)
|
| 282 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 283 |
+
Vx3 = self.x + self.y
|
| 284 |
+
Vy3 = 2*self.x + self.y # ∂P/∂y = 1, ∂Q/∂x = 2 → not equal
|
| 285 |
+
try:
|
| 286 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 287 |
+
self.linear_analyzer.display_results(phi3, Vx3, Vy3)
|
| 288 |
+
except Exception as e:
|
| 289 |
+
print(f"Expected error for non-gradient field: {e}")
|
| 290 |
+
print("This field cannot have a potential function!")
|
| 291 |
+
print("-" * 50)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def main():
|
| 295 |
+
# Run the complete analysis
|
| 296 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 297 |
+
|
| 298 |
+
# Run numerical examples
|
| 299 |
+
print("\n" + "=" * 60)
|
| 300 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 301 |
+
print("=" * 60)
|
| 302 |
+
|
| 303 |
+
numerical_examples = NumericalExamples()
|
| 304 |
+
numerical_examples.run_case1_examples()
|
| 305 |
+
numerical_examples.run_case2_examples()
|
| 306 |
+
|
| 307 |
+
# Additional demonstration of the key insight
|
| 308 |
+
print("\n" + "=" * 60)
|
| 309 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 310 |
+
print("=" * 60)
|
| 311 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 312 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 313 |
+
print("• The potential function is: φ(x,y) = ½a₁x² + b₁xy + ½b₂y² + c₁x + c₂y")
|
| 314 |
+
print("• This matches equations (3.44) and (3.45) from the case study")
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
if __name__ == "__main__":
|
| 318 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_002.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
return diff_x == 0 and diff_y == 0, diff_x, diff_y
|
| 29 |
+
|
| 30 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 31 |
+
"""Display the results in a formatted way"""
|
| 32 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 33 |
+
print(f"Potential Function: φ(x,y) = {phi}")
|
| 34 |
+
|
| 35 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 36 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 37 |
+
if not is_valid:
|
| 38 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 39 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 40 |
+
print("-" * 50)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 44 |
+
"""Case 1: One component is constant"""
|
| 45 |
+
|
| 46 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.constant_component = constant_component.lower()
|
| 49 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 50 |
+
|
| 51 |
+
# Define unknown functions
|
| 52 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 53 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 54 |
+
|
| 55 |
+
def find_potential(self) -> sp.Expr:
|
| 56 |
+
if self.constant_component == 'x':
|
| 57 |
+
# F(x,y) = [c, F_y(y)]
|
| 58 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 59 |
+
else: # constant y-component
|
| 60 |
+
# F(x,y) = [F_x(x), c]
|
| 61 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 62 |
+
|
| 63 |
+
return phi
|
| 64 |
+
|
| 65 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 66 |
+
if self.constant_component == 'x':
|
| 67 |
+
return self.c, self.F_y
|
| 68 |
+
else:
|
| 69 |
+
return self.F_x, self.c
|
| 70 |
+
|
| 71 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 72 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 73 |
+
if Vx.is_constant(): # Constant x-component
|
| 74 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 75 |
+
elif Vy.is_constant(): # Constant y-component
|
| 76 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 77 |
+
else:
|
| 78 |
+
raise ValueError("One component must be constant")
|
| 79 |
+
return phi
|
| 80 |
+
|
| 81 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 82 |
+
"""Return specific cases with actual functions"""
|
| 83 |
+
cases = {}
|
| 84 |
+
|
| 85 |
+
# Case 1a: Constant x-component with specific F_y
|
| 86 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 87 |
+
Vx1, Vy1 = self.c, F_y_specific
|
| 88 |
+
phi1 = self.find_potential_for_specific_field(self.c, F_y_specific)
|
| 89 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 90 |
+
|
| 91 |
+
# Case 1b: Constant y-component with specific F_x
|
| 92 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 93 |
+
Vx2, Vy2 = F_x_specific, self.c
|
| 94 |
+
phi2 = self.find_potential_for_specific_field(F_x_specific, self.c)
|
| 95 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 96 |
+
|
| 97 |
+
return cases
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 101 |
+
"""Case 2: Both components are linear functions"""
|
| 102 |
+
|
| 103 |
+
def __init__(self):
|
| 104 |
+
super().__init__()
|
| 105 |
+
# Define constants for linear functions
|
| 106 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 107 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 108 |
+
|
| 109 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 110 |
+
"""Find potential function for given linear vector field"""
|
| 111 |
+
if Vx is None or Vy is None:
|
| 112 |
+
Vx, Vy = self.get_general_linear_field()
|
| 113 |
+
|
| 114 |
+
# Use the method from the case study
|
| 115 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 116 |
+
return phi
|
| 117 |
+
|
| 118 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 119 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 120 |
+
# Method 1: Integrate Vx with respect to x, then determine y-dependent part
|
| 121 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 122 |
+
|
| 123 |
+
# The integration constant might be a function of y
|
| 124 |
+
# Differentiate with respect to y and compare with Vy
|
| 125 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 126 |
+
remaining = Vy - diff_phi_x_y
|
| 127 |
+
|
| 128 |
+
# Integrate remaining with respect to y
|
| 129 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 130 |
+
|
| 131 |
+
# Combine results
|
| 132 |
+
phi = phi_x + phi_y
|
| 133 |
+
|
| 134 |
+
return phi
|
| 135 |
+
|
| 136 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 137 |
+
return self.get_general_linear_field()
|
| 138 |
+
|
| 139 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 140 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 141 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 142 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 143 |
+
return Vx, Vy
|
| 144 |
+
|
| 145 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 146 |
+
"""Return the condition for the field to be a gradient field"""
|
| 147 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 148 |
+
Vx, Vy = self.get_general_linear_field()
|
| 149 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 150 |
+
return sp.simplify(condition)
|
| 151 |
+
|
| 152 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 153 |
+
"""Find potential for a specific numeric vector field"""
|
| 154 |
+
return self._find_potential_2d(Vx, Vy)
|
| 155 |
+
|
| 156 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 157 |
+
"""Return specific cases where the field is a gradient field"""
|
| 158 |
+
cases = {}
|
| 159 |
+
|
| 160 |
+
# Case 2a: a2 = b1 (from the case study)
|
| 161 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 162 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 163 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 164 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 165 |
+
|
| 166 |
+
# Case 2b: b2 = a1 (alternative symmetric case)
|
| 167 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 168 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 169 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 170 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 171 |
+
|
| 172 |
+
return cases
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
class GradientFieldFactory:
|
| 176 |
+
"""Factory class to create different types of gradient fields"""
|
| 177 |
+
|
| 178 |
+
@staticmethod
|
| 179 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 180 |
+
return ConstantComponentField(component, constant)
|
| 181 |
+
|
| 182 |
+
@staticmethod
|
| 183 |
+
def create_linear_component_field():
|
| 184 |
+
return LinearComponentField()
|
| 185 |
+
|
| 186 |
+
@staticmethod
|
| 187 |
+
def analyze_all_cases():
|
| 188 |
+
"""Analyze all cases from the case study"""
|
| 189 |
+
print("=" * 60)
|
| 190 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 191 |
+
print("=" * 60)
|
| 192 |
+
|
| 193 |
+
# Case 1: Constant component
|
| 194 |
+
print("\nCASE 1: One Component is Constant")
|
| 195 |
+
print("-" * 40)
|
| 196 |
+
|
| 197 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 198 |
+
phi1 = case1_x.find_potential()
|
| 199 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 200 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 201 |
+
|
| 202 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 203 |
+
phi2 = case1_y.find_potential()
|
| 204 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 205 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 206 |
+
|
| 207 |
+
# Show specific examples
|
| 208 |
+
print("\nSpecific Examples for Case 1:")
|
| 209 |
+
examples1 = case1_x.get_specific_cases()
|
| 210 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 211 |
+
print(f"{case_name}:")
|
| 212 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 213 |
+
|
| 214 |
+
# Case 2: Linear components
|
| 215 |
+
print("\nCASE 2: Both Components are Linear")
|
| 216 |
+
print("-" * 40)
|
| 217 |
+
|
| 218 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 219 |
+
|
| 220 |
+
# Show gradient condition
|
| 221 |
+
condition = case2.get_gradient_condition()
|
| 222 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 223 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 224 |
+
|
| 225 |
+
# Show specific gradient cases
|
| 226 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 227 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 228 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 229 |
+
print(f"{case_name}:")
|
| 230 |
+
case2.display_results(phi, Vx, Vy)
|
| 231 |
+
|
| 232 |
+
return case1_x, case1_y, case2
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class NumericalExamples:
|
| 236 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 237 |
+
|
| 238 |
+
def __init__(self):
|
| 239 |
+
self.x, self.y = sp.symbols('x y')
|
| 240 |
+
self.constant_analyzer = ConstantComponentField()
|
| 241 |
+
self.linear_analyzer = LinearComponentField()
|
| 242 |
+
|
| 243 |
+
def run_case1_examples(self):
|
| 244 |
+
"""Run numerical examples for Case 1"""
|
| 245 |
+
print("\nNumerical Examples - Case 1:")
|
| 246 |
+
print("-" * 40)
|
| 247 |
+
|
| 248 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 249 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 250 |
+
Vx1 = 2
|
| 251 |
+
Vy1 = 3 * self.y**2
|
| 252 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 253 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 254 |
+
|
| 255 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 256 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 257 |
+
Vx2 = sp.sin(self.x)
|
| 258 |
+
Vy2 = 5
|
| 259 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 260 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 261 |
+
|
| 262 |
+
def run_case2_examples(self):
|
| 263 |
+
"""Run numerical examples for Case 2"""
|
| 264 |
+
print("\nNumerical Examples - Case 2:")
|
| 265 |
+
print("-" * 40)
|
| 266 |
+
|
| 267 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 268 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 269 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 270 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 271 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 272 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 273 |
+
|
| 274 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 275 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 276 |
+
Vx2 = self.x + 2*self.y
|
| 277 |
+
Vy2 = 2*self.x + 3*self.y
|
| 278 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 279 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 280 |
+
|
| 281 |
+
# Example 3: Non-gradient field (should fail verification)
|
| 282 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 283 |
+
Vx3 = self.x + self.y
|
| 284 |
+
Vy3 = 2*self.x + self.y # ∂P/∂y = 1, ∂Q/∂x = 2 → not equal
|
| 285 |
+
try:
|
| 286 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 287 |
+
self.linear_analyzer.display_results(phi3, Vx3, Vy3)
|
| 288 |
+
except Exception as e:
|
| 289 |
+
print(f"Expected error for non-gradient field: {e}")
|
| 290 |
+
print("This field cannot have a potential function!")
|
| 291 |
+
print("-" * 50)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def main():
|
| 295 |
+
# Run the complete analysis
|
| 296 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 297 |
+
|
| 298 |
+
# Run numerical examples
|
| 299 |
+
print("\n" + "=" * 60)
|
| 300 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 301 |
+
print("=" * 60)
|
| 302 |
+
|
| 303 |
+
numerical_examples = NumericalExamples()
|
| 304 |
+
numerical_examples.run_case1_examples()
|
| 305 |
+
numerical_examples.run_case2_examples()
|
| 306 |
+
|
| 307 |
+
# Additional demonstration of the key insight
|
| 308 |
+
print("\n" + "=" * 60)
|
| 309 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 310 |
+
print("=" * 60)
|
| 311 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 312 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 313 |
+
print("• The potential function is: φ(x,y) = ½a₁x² + b₁xy + ½b₂y² + c₁x + c₂y")
|
| 314 |
+
print("• This matches equations (3.44) and (3.45) from the case study")
|
| 315 |
+
|
| 316 |
+
# Show the exact forms from the case study
|
| 317 |
+
print("\nEXACT FORMS FROM CASE STUDY (Equations 3.44, 3.45):")
|
| 318 |
+
x, y = sp.symbols('x y')
|
| 319 |
+
a1, b1, c1, a2, b2, c2 = sp.symbols('a1 b1 c1 a2 b2 c2')
|
| 320 |
+
|
| 321 |
+
print("Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 322 |
+
print("Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
if __name__ == "__main__":
|
| 326 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_003.py
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
return diff_x == 0 and diff_y == 0, diff_x, diff_y
|
| 29 |
+
|
| 30 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 31 |
+
"""Display the results in a formatted way"""
|
| 32 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 33 |
+
print(f"Potential Function: φ(x,y) = {phi}")
|
| 34 |
+
|
| 35 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 36 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 37 |
+
if not is_valid:
|
| 38 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 39 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 40 |
+
print("-" * 50)
|
| 41 |
+
|
| 42 |
+
def _is_constant_expression(self, expr: sp.Expr) -> bool:
|
| 43 |
+
"""Check if an expression is constant (doesn't depend on x or y)"""
|
| 44 |
+
return len(expr.free_symbols) == 0
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 48 |
+
"""Case 1: One component is constant"""
|
| 49 |
+
|
| 50 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 51 |
+
super().__init__()
|
| 52 |
+
self.constant_component = constant_component.lower()
|
| 53 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 54 |
+
|
| 55 |
+
# Define unknown functions
|
| 56 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 57 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 58 |
+
|
| 59 |
+
def find_potential(self) -> sp.Expr:
|
| 60 |
+
if self.constant_component == 'x':
|
| 61 |
+
# F(x,y) = [c, F_y(y)]
|
| 62 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 63 |
+
else: # constant y-component
|
| 64 |
+
# F(x,y) = [F_x(x), c]
|
| 65 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 66 |
+
|
| 67 |
+
return phi
|
| 68 |
+
|
| 69 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 70 |
+
if self.constant_component == 'x':
|
| 71 |
+
return self.c, self.F_y
|
| 72 |
+
else:
|
| 73 |
+
return self.F_x, self.c
|
| 74 |
+
|
| 75 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 76 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 77 |
+
# Check which component is constant
|
| 78 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 79 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 80 |
+
|
| 81 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 82 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 83 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 84 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 85 |
+
|
| 86 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 87 |
+
# F(x,y) = [constant, Vy]
|
| 88 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 89 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 90 |
+
# F(x,y) = [Vx, constant]
|
| 91 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 92 |
+
else:
|
| 93 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 94 |
+
return phi
|
| 95 |
+
|
| 96 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 97 |
+
"""Return specific cases with actual functions"""
|
| 98 |
+
cases = {}
|
| 99 |
+
|
| 100 |
+
# Case 1a: Constant x-component with specific F_y
|
| 101 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 102 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 103 |
+
Vy1 = F_y_specific
|
| 104 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 105 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 106 |
+
|
| 107 |
+
# Case 1b: Constant y-component with specific F_x
|
| 108 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 109 |
+
Vx2 = F_x_specific
|
| 110 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 111 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 112 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 113 |
+
|
| 114 |
+
return cases
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 118 |
+
"""Case 2: Both components are linear functions"""
|
| 119 |
+
|
| 120 |
+
def __init__(self):
|
| 121 |
+
super().__init__()
|
| 122 |
+
# Define constants for linear functions
|
| 123 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 124 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 125 |
+
|
| 126 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 127 |
+
"""Find potential function for given linear vector field"""
|
| 128 |
+
if Vx is None or Vy is None:
|
| 129 |
+
Vx, Vy = self.get_general_linear_field()
|
| 130 |
+
|
| 131 |
+
# Use the method from the case study
|
| 132 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 133 |
+
return phi
|
| 134 |
+
|
| 135 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 136 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 137 |
+
# Method 1: Integrate Vx with respect to x, then determine y-dependent part
|
| 138 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 139 |
+
|
| 140 |
+
# The integration constant might be a function of y
|
| 141 |
+
# Differentiate with respect to y and compare with Vy
|
| 142 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 143 |
+
remaining = Vy - diff_phi_x_y
|
| 144 |
+
|
| 145 |
+
# Integrate remaining with respect to y
|
| 146 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 147 |
+
|
| 148 |
+
# Combine results
|
| 149 |
+
phi = phi_x + phi_y
|
| 150 |
+
|
| 151 |
+
return phi
|
| 152 |
+
|
| 153 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 154 |
+
return self.get_general_linear_field()
|
| 155 |
+
|
| 156 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 157 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 158 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 159 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 160 |
+
return Vx, Vy
|
| 161 |
+
|
| 162 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 163 |
+
"""Return the condition for the field to be a gradient field"""
|
| 164 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 165 |
+
Vx, Vy = self.get_general_linear_field()
|
| 166 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 167 |
+
return sp.simplify(condition)
|
| 168 |
+
|
| 169 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 170 |
+
"""Find potential for a specific numeric vector field"""
|
| 171 |
+
return self._find_potential_2d(Vx, Vy)
|
| 172 |
+
|
| 173 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 174 |
+
"""Return specific cases where the field is a gradient field"""
|
| 175 |
+
cases = {}
|
| 176 |
+
|
| 177 |
+
# Case 2a: a2 = b1 (from the case study)
|
| 178 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 179 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 180 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 181 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 182 |
+
|
| 183 |
+
# Case 2b: b2 = a1 (alternative symmetric case)
|
| 184 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 185 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 186 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 187 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 188 |
+
|
| 189 |
+
return cases
|
| 190 |
+
|
| 191 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 192 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 193 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 194 |
+
return sp.simplify(condition) == 0
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
class GradientFieldFactory:
|
| 198 |
+
"""Factory class to create different types of gradient fields"""
|
| 199 |
+
|
| 200 |
+
@staticmethod
|
| 201 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 202 |
+
return ConstantComponentField(component, constant)
|
| 203 |
+
|
| 204 |
+
@staticmethod
|
| 205 |
+
def create_linear_component_field():
|
| 206 |
+
return LinearComponentField()
|
| 207 |
+
|
| 208 |
+
@staticmethod
|
| 209 |
+
def analyze_all_cases():
|
| 210 |
+
"""Analyze all cases from the case study"""
|
| 211 |
+
print("=" * 60)
|
| 212 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 213 |
+
print("=" * 60)
|
| 214 |
+
|
| 215 |
+
# Case 1: Constant component
|
| 216 |
+
print("\nCASE 1: One Component is Constant")
|
| 217 |
+
print("-" * 40)
|
| 218 |
+
|
| 219 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 220 |
+
phi1 = case1_x.find_potential()
|
| 221 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 222 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 223 |
+
|
| 224 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 225 |
+
phi2 = case1_y.find_potential()
|
| 226 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 227 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 228 |
+
|
| 229 |
+
# Show specific examples
|
| 230 |
+
print("\nSpecific Examples for Case 1:")
|
| 231 |
+
examples1 = case1_x.get_specific_cases()
|
| 232 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 233 |
+
print(f"{case_name}:")
|
| 234 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 235 |
+
|
| 236 |
+
# Case 2: Linear components
|
| 237 |
+
print("\nCASE 2: Both Components are Linear")
|
| 238 |
+
print("-" * 40)
|
| 239 |
+
|
| 240 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 241 |
+
|
| 242 |
+
# Show gradient condition
|
| 243 |
+
condition = case2.get_gradient_condition()
|
| 244 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 245 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 246 |
+
|
| 247 |
+
# Show specific gradient cases
|
| 248 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 249 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 250 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 251 |
+
print(f"{case_name}:")
|
| 252 |
+
case2.display_results(phi, Vx, Vy)
|
| 253 |
+
|
| 254 |
+
return case1_x, case1_y, case2
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
class NumericalExamples:
|
| 258 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 259 |
+
|
| 260 |
+
def __init__(self):
|
| 261 |
+
self.x, self.y = sp.symbols('x y')
|
| 262 |
+
self.constant_analyzer = ConstantComponentField()
|
| 263 |
+
self.linear_analyzer = LinearComponentField()
|
| 264 |
+
|
| 265 |
+
def run_case1_examples(self):
|
| 266 |
+
"""Run numerical examples for Case 1"""
|
| 267 |
+
print("\nNumerical Examples - Case 1:")
|
| 268 |
+
print("-" * 40)
|
| 269 |
+
|
| 270 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 271 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 272 |
+
Vx1 = 2
|
| 273 |
+
Vy1 = 3 * self.y**2
|
| 274 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 275 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 276 |
+
|
| 277 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 278 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 279 |
+
Vx2 = sp.sin(self.x)
|
| 280 |
+
Vy2 = 5
|
| 281 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 282 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 283 |
+
|
| 284 |
+
def run_case2_examples(self):
|
| 285 |
+
"""Run numerical examples for Case 2"""
|
| 286 |
+
print("\nNumerical Examples - Case 2:")
|
| 287 |
+
print("-" * 40)
|
| 288 |
+
|
| 289 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 290 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 291 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 292 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 293 |
+
|
| 294 |
+
# Check if it's a gradient field first
|
| 295 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 296 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 297 |
+
|
| 298 |
+
if is_gradient:
|
| 299 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 300 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 301 |
+
else:
|
| 302 |
+
print("This field cannot have a potential function!")
|
| 303 |
+
print("-" * 50)
|
| 304 |
+
|
| 305 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 306 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 307 |
+
Vx2 = self.x + 2*self.y
|
| 308 |
+
Vy2 = 2*self.x + 3*self.y
|
| 309 |
+
|
| 310 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 311 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 312 |
+
|
| 313 |
+
if is_gradient2:
|
| 314 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 315 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 316 |
+
else:
|
| 317 |
+
print("This field cannot have a potential function!")
|
| 318 |
+
print("-" * 50)
|
| 319 |
+
|
| 320 |
+
# Example 3: Non-gradient field (should fail verification)
|
| 321 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 322 |
+
Vx3 = self.x + self.y
|
| 323 |
+
Vy3 = 2*self.x + self.y # ∂P/∂y = 1, ∂Q/∂x = 2 → not equal
|
| 324 |
+
|
| 325 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 326 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 327 |
+
|
| 328 |
+
if is_gradient3:
|
| 329 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 330 |
+
self.linear_analyzer.display_results(phi3, Vx3, Vy3)
|
| 331 |
+
else:
|
| 332 |
+
print("This field cannot have a potential function!")
|
| 333 |
+
print("Reason: ∂P/∂y = 1 ≠ ∂Q/∂x = 2")
|
| 334 |
+
print("-" * 50)
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def main():
|
| 338 |
+
# Run the complete analysis
|
| 339 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 340 |
+
|
| 341 |
+
# Run numerical examples
|
| 342 |
+
print("\n" + "=" * 60)
|
| 343 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 344 |
+
print("=" * 60)
|
| 345 |
+
|
| 346 |
+
numerical_examples = NumericalExamples()
|
| 347 |
+
numerical_examples.run_case1_examples()
|
| 348 |
+
numerical_examples.run_case2_examples()
|
| 349 |
+
|
| 350 |
+
# Additional demonstration of the key insight
|
| 351 |
+
print("\n" + "=" * 60)
|
| 352 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 353 |
+
print("=" * 60)
|
| 354 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 355 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 356 |
+
print("• The potential function is: φ(x,y) = ½a₁x² + b₁xy + ½b₂y² + c₁x + c₂y")
|
| 357 |
+
print("• This matches equations (3.44) and (3.45) from the case study")
|
| 358 |
+
|
| 359 |
+
# Show the exact forms from the case study
|
| 360 |
+
print("\nEXACT FORMS FROM CASE STUDY (Equations 3.44, 3.45):")
|
| 361 |
+
x, y = sp.symbols('x y')
|
| 362 |
+
a1, b1, c1, a2, b2, c2 = sp.symbols('a1 b1 c1 a2 b2 c2')
|
| 363 |
+
|
| 364 |
+
print("Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 365 |
+
phi_form1 = a1*x**2/2 + b2*y**2/2 + c2*y + x*(b1*y + c1)
|
| 366 |
+
print(f" = {phi_form1}")
|
| 367 |
+
|
| 368 |
+
print("Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 369 |
+
phi_form2 = a2*x**2/2 + b1*y**2/2 + c1*y + x*(a1*y + c2)
|
| 370 |
+
print(f" = {phi_form2}")
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
if __name__ == "__main__":
|
| 374 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_004.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
return diff_x == 0 and diff_y == 0, diff_x, diff_y
|
| 29 |
+
|
| 30 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 31 |
+
"""Display the results in a formatted way"""
|
| 32 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 33 |
+
print(f"Potential Function: φ(x,y) = {phi}")
|
| 34 |
+
|
| 35 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 36 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 37 |
+
if not is_valid:
|
| 38 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 39 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 40 |
+
print("-" * 50)
|
| 41 |
+
|
| 42 |
+
def _is_constant_expression(self, expr: sp.Expr) -> bool:
|
| 43 |
+
"""Check if an expression is constant (doesn't depend on x or y)"""
|
| 44 |
+
return len(expr.free_symbols) == 0
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 48 |
+
"""Case 1: One component is constant"""
|
| 49 |
+
|
| 50 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 51 |
+
super().__init__()
|
| 52 |
+
self.constant_component = constant_component.lower()
|
| 53 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 54 |
+
|
| 55 |
+
# Define unknown functions
|
| 56 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 57 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 58 |
+
|
| 59 |
+
def find_potential(self) -> sp.Expr:
|
| 60 |
+
if self.constant_component == 'x':
|
| 61 |
+
# F(x,y) = [c, F_y(y)]
|
| 62 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 63 |
+
else: # constant y-component
|
| 64 |
+
# F(x,y) = [F_x(x), c]
|
| 65 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 66 |
+
|
| 67 |
+
return phi
|
| 68 |
+
|
| 69 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 70 |
+
if self.constant_component == 'x':
|
| 71 |
+
return self.c, self.F_y
|
| 72 |
+
else:
|
| 73 |
+
return self.F_x, self.c
|
| 74 |
+
|
| 75 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 76 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 77 |
+
# Check which component is constant
|
| 78 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 79 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 80 |
+
|
| 81 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 82 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 83 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 84 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 85 |
+
|
| 86 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 87 |
+
# F(x,y) = [constant, Vy]
|
| 88 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 89 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 90 |
+
# F(x,y) = [Vx, constant]
|
| 91 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 92 |
+
else:
|
| 93 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 94 |
+
return phi
|
| 95 |
+
|
| 96 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 97 |
+
"""Return specific cases with actual functions"""
|
| 98 |
+
cases = {}
|
| 99 |
+
|
| 100 |
+
# Case 1a: Constant x-component with specific F_y
|
| 101 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 102 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 103 |
+
Vy1 = F_y_specific
|
| 104 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 105 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 106 |
+
|
| 107 |
+
# Case 1b: Constant y-component with specific F_x
|
| 108 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 109 |
+
Vx2 = F_x_specific
|
| 110 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 111 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 112 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 113 |
+
|
| 114 |
+
return cases
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 118 |
+
"""Case 2: Both components are linear functions"""
|
| 119 |
+
|
| 120 |
+
def __init__(self):
|
| 121 |
+
super().__init__()
|
| 122 |
+
# Define constants for linear functions
|
| 123 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 124 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 125 |
+
|
| 126 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 127 |
+
"""Find potential function for given linear vector field"""
|
| 128 |
+
if Vx is None or Vy is None:
|
| 129 |
+
Vx, Vy = self.get_general_linear_field()
|
| 130 |
+
|
| 131 |
+
# Use the method from the case study
|
| 132 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 133 |
+
return phi
|
| 134 |
+
|
| 135 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 136 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 137 |
+
# Method: Integrate Vx with respect to x
|
| 138 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 139 |
+
|
| 140 |
+
# Differentiate with respect to y and compare with Vy
|
| 141 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 142 |
+
remaining = Vy - diff_phi_x_y
|
| 143 |
+
|
| 144 |
+
# Integrate remaining with respect to y
|
| 145 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 146 |
+
|
| 147 |
+
# Combine results (no additional constants needed)
|
| 148 |
+
phi = phi_x + phi_y
|
| 149 |
+
|
| 150 |
+
return phi
|
| 151 |
+
|
| 152 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 153 |
+
return self.get_general_linear_field()
|
| 154 |
+
|
| 155 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 156 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 157 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 158 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 159 |
+
return Vx, Vy
|
| 160 |
+
|
| 161 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 162 |
+
"""Return the condition for the field to be a gradient field"""
|
| 163 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 164 |
+
Vx, Vy = self.get_general_linear_field()
|
| 165 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 166 |
+
return sp.simplify(condition)
|
| 167 |
+
|
| 168 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 169 |
+
"""Find potential for a specific numeric vector field using correct integration"""
|
| 170 |
+
# For linear fields, we can use the formula from the case study
|
| 171 |
+
# First extract coefficients
|
| 172 |
+
coeffs = self._extract_linear_coefficients(Vx, Vy)
|
| 173 |
+
|
| 174 |
+
if coeffs['a2'] != coeffs['b1']:
|
| 175 |
+
raise ValueError("Field is not a gradient field - gradient condition not satisfied")
|
| 176 |
+
|
| 177 |
+
# Use the formula: φ(x,y) = ½a₁x² + b₁xy + ½b₂y² + c₁x + c₂y
|
| 178 |
+
phi = (coeffs['a1'] * self.x**2 / 2 +
|
| 179 |
+
coeffs['b1'] * self.x * self.y +
|
| 180 |
+
coeffs['b2'] * self.y**2 / 2 +
|
| 181 |
+
coeffs['c1'] * self.x +
|
| 182 |
+
coeffs['c2'] * self.y)
|
| 183 |
+
|
| 184 |
+
return phi
|
| 185 |
+
|
| 186 |
+
def _extract_linear_coefficients(self, Vx: sp.Expr, Vy: sp.Expr) -> Dict[str, float]:
|
| 187 |
+
"""Extract coefficients from linear vector field components"""
|
| 188 |
+
# For Vx = a1*x + b1*y + c1
|
| 189 |
+
a1 = float(sp.diff(Vx, self.x).subs([(self.x, 1), (self.y, 0)])) if self.x in Vx.free_symbols else 0
|
| 190 |
+
b1 = float(sp.diff(Vx, self.y).subs([(self.x, 0), (self.y, 1)])) if self.y in Vx.free_symbols else 0
|
| 191 |
+
c1 = float(Vx.subs([(self.x, 0), (self.y, 0)]))
|
| 192 |
+
|
| 193 |
+
# For Vy = a2*x + b2*y + c2
|
| 194 |
+
a2 = float(sp.diff(Vy, self.x).subs([(self.x, 1), (self.y, 0)])) if self.x in Vy.free_symbols else 0
|
| 195 |
+
b2 = float(sp.diff(Vy, self.y).subs([(self.x, 0), (self.y, 1)])) if self.y in Vy.free_symbols else 0
|
| 196 |
+
c2 = float(Vy.subs([(self.x, 0), (self.y, 0)]))
|
| 197 |
+
|
| 198 |
+
return {'a1': a1, 'b1': b1, 'c1': c1, 'a2': a2, 'b2': b2, 'c2': c2}
|
| 199 |
+
|
| 200 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 201 |
+
"""Return specific cases where the field is a gradient field"""
|
| 202 |
+
cases = {}
|
| 203 |
+
|
| 204 |
+
# Case 2a: a2 = b1 (from the case study)
|
| 205 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 206 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 207 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 208 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 209 |
+
|
| 210 |
+
# Case 2b: b2 = a1 (alternative symmetric case)
|
| 211 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 212 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 213 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 214 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 215 |
+
|
| 216 |
+
return cases
|
| 217 |
+
|
| 218 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 219 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 220 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 221 |
+
return sp.simplify(condition) == 0
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class GradientFieldFactory:
|
| 225 |
+
"""Factory class to create different types of gradient fields"""
|
| 226 |
+
|
| 227 |
+
@staticmethod
|
| 228 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 229 |
+
return ConstantComponentField(component, constant)
|
| 230 |
+
|
| 231 |
+
@staticmethod
|
| 232 |
+
def create_linear_component_field():
|
| 233 |
+
return LinearComponentField()
|
| 234 |
+
|
| 235 |
+
@staticmethod
|
| 236 |
+
def analyze_all_cases():
|
| 237 |
+
"""Analyze all cases from the case study"""
|
| 238 |
+
print("=" * 60)
|
| 239 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 240 |
+
print("=" * 60)
|
| 241 |
+
|
| 242 |
+
# Case 1: Constant component
|
| 243 |
+
print("\nCASE 1: One Component is Constant")
|
| 244 |
+
print("-" * 40)
|
| 245 |
+
|
| 246 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 247 |
+
phi1 = case1_x.find_potential()
|
| 248 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 249 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 250 |
+
|
| 251 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 252 |
+
phi2 = case1_y.find_potential()
|
| 253 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 254 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 255 |
+
|
| 256 |
+
# Show specific examples
|
| 257 |
+
print("\nSpecific Examples for Case 1:")
|
| 258 |
+
examples1 = case1_x.get_specific_cases()
|
| 259 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 260 |
+
print(f"{case_name}:")
|
| 261 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 262 |
+
|
| 263 |
+
# Case 2: Linear components
|
| 264 |
+
print("\nCASE 2: Both Components are Linear")
|
| 265 |
+
print("-" * 40)
|
| 266 |
+
|
| 267 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 268 |
+
|
| 269 |
+
# Show gradient condition
|
| 270 |
+
condition = case2.get_gradient_condition()
|
| 271 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 272 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 273 |
+
|
| 274 |
+
# Show specific gradient cases
|
| 275 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 276 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 277 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 278 |
+
print(f"{case_name}:")
|
| 279 |
+
case2.display_results(phi, Vx, Vy)
|
| 280 |
+
|
| 281 |
+
return case1_x, case1_y, case2
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
class NumericalExamples:
|
| 285 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 286 |
+
|
| 287 |
+
def __init__(self):
|
| 288 |
+
self.x, self.y = sp.symbols('x y')
|
| 289 |
+
self.constant_analyzer = ConstantComponentField()
|
| 290 |
+
self.linear_analyzer = LinearComponentField()
|
| 291 |
+
|
| 292 |
+
def run_case1_examples(self):
|
| 293 |
+
"""Run numerical examples for Case 1"""
|
| 294 |
+
print("\nNumerical Examples - Case 1:")
|
| 295 |
+
print("-" * 40)
|
| 296 |
+
|
| 297 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 298 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 299 |
+
Vx1 = 2
|
| 300 |
+
Vy1 = 3 * self.y**2
|
| 301 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 302 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 303 |
+
|
| 304 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 305 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 306 |
+
Vx2 = sp.sin(self.x)
|
| 307 |
+
Vy2 = 5
|
| 308 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 309 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 310 |
+
|
| 311 |
+
def run_case2_examples(self):
|
| 312 |
+
"""Run numerical examples for Case 2"""
|
| 313 |
+
print("\nNumerical Examples - Case 2:")
|
| 314 |
+
print("-" * 40)
|
| 315 |
+
|
| 316 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 317 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 318 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 319 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 320 |
+
|
| 321 |
+
# Check if it's a gradient field first
|
| 322 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 323 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 324 |
+
|
| 325 |
+
if is_gradient:
|
| 326 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 327 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 328 |
+
else:
|
| 329 |
+
print("This field cannot have a potential function!")
|
| 330 |
+
print("-" * 50)
|
| 331 |
+
|
| 332 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 333 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 334 |
+
Vx2 = self.x + 2*self.y
|
| 335 |
+
Vy2 = 2*self.x + 3*self.y
|
| 336 |
+
|
| 337 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 338 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 339 |
+
|
| 340 |
+
if is_gradient2:
|
| 341 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 342 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 343 |
+
else:
|
| 344 |
+
print("This field cannot have a potential function!")
|
| 345 |
+
print("-" * 50)
|
| 346 |
+
|
| 347 |
+
# Example 3: Non-gradient field (should fail)
|
| 348 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 349 |
+
Vx3 = self.x + self.y
|
| 350 |
+
Vy3 = 2*self.x + self.y # ∂P/∂y = 1, ∂Q/∂x = 2 → not equal
|
| 351 |
+
|
| 352 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 353 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 354 |
+
|
| 355 |
+
if is_gradient3:
|
| 356 |
+
try:
|
| 357 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 358 |
+
self.linear_analyzer.display_results(phi3, Vx3, Vy3)
|
| 359 |
+
except ValueError as e:
|
| 360 |
+
print(f"Error (expected): {e}")
|
| 361 |
+
print("This field cannot have a potential function!")
|
| 362 |
+
else:
|
| 363 |
+
print("This field cannot have a potential function!")
|
| 364 |
+
print("Reason: ∂P/∂y = 1 ≠ ∂Q/∂x = 2")
|
| 365 |
+
print("-" * 50)
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
def main():
|
| 369 |
+
# Run the complete analysis
|
| 370 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 371 |
+
|
| 372 |
+
# Run numerical examples
|
| 373 |
+
print("\n" + "=" * 60)
|
| 374 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 375 |
+
print("=" * 60)
|
| 376 |
+
|
| 377 |
+
numerical_examples = NumericalExamples()
|
| 378 |
+
numerical_examples.run_case1_examples()
|
| 379 |
+
numerical_examples.run_case2_examples()
|
| 380 |
+
|
| 381 |
+
# Additional demonstration of the key insight
|
| 382 |
+
print("\n" + "=" * 60)
|
| 383 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 384 |
+
print("=" * 60)
|
| 385 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 386 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 387 |
+
print("• The potential function is: φ(x,y) = ½a₁x² + b₁xy + ½b₂y² + c₁x + c₂y")
|
| 388 |
+
print("• This matches equations (3.44) and (3.45) from the case study")
|
| 389 |
+
|
| 390 |
+
# Show the exact forms from the case study
|
| 391 |
+
print("\nEXACT FORMS FROM CASE STUDY (Equations 3.44, 3.45):")
|
| 392 |
+
x, y = sp.symbols('x y')
|
| 393 |
+
a1, b1, c1, a2, b2, c2 = sp.symbols('a1 b1 c1 a2 b2 c2')
|
| 394 |
+
|
| 395 |
+
print("Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 396 |
+
phi_form1 = a1*x**2/2 + b2*y**2/2 + c2*y + x*(b1*y + c1)
|
| 397 |
+
print(f" = {phi_form1}")
|
| 398 |
+
|
| 399 |
+
print("Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 400 |
+
phi_form2 = a2*x**2/2 + b1*y**2/2 + c1*y + x*(a1*y + c2)
|
| 401 |
+
print(f" = {phi_form2}")
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
if __name__ == "__main__":
|
| 405 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_005.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
return diff_x == 0 and diff_y == 0, diff_x, diff_y
|
| 29 |
+
|
| 30 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 31 |
+
"""Display the results in a formatted way"""
|
| 32 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 33 |
+
print(f"Potential Function: φ(x,y) = {phi}")
|
| 34 |
+
|
| 35 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 36 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 37 |
+
if not is_valid:
|
| 38 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 39 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 40 |
+
print("-" * 50)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 44 |
+
"""Case 1: One component is constant"""
|
| 45 |
+
|
| 46 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.constant_component = constant_component.lower()
|
| 49 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 50 |
+
|
| 51 |
+
# Define unknown functions
|
| 52 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 53 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 54 |
+
|
| 55 |
+
def find_potential(self) -> sp.Expr:
|
| 56 |
+
if self.constant_component == 'x':
|
| 57 |
+
# F(x,y) = [c, F_y(y)]
|
| 58 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 59 |
+
else: # constant y-component
|
| 60 |
+
# F(x,y) = [F_x(x), c]
|
| 61 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 62 |
+
|
| 63 |
+
return phi
|
| 64 |
+
|
| 65 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 66 |
+
if self.constant_component == 'x':
|
| 67 |
+
return self.c, self.F_y
|
| 68 |
+
else:
|
| 69 |
+
return self.F_x, self.c
|
| 70 |
+
|
| 71 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 72 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 73 |
+
# Check which component is constant
|
| 74 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 75 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 76 |
+
|
| 77 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 78 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 79 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 80 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 81 |
+
|
| 82 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 83 |
+
# F(x,y) = [constant, Vy]
|
| 84 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 85 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 86 |
+
# F(x,y) = [Vx, constant]
|
| 87 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 88 |
+
else:
|
| 89 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 90 |
+
return phi
|
| 91 |
+
|
| 92 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 93 |
+
"""Return specific cases with actual functions"""
|
| 94 |
+
cases = {}
|
| 95 |
+
|
| 96 |
+
# Case 1a: Constant x-component with specific F_y
|
| 97 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 98 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 99 |
+
Vy1 = F_y_specific
|
| 100 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 101 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 102 |
+
|
| 103 |
+
# Case 1b: Constant y-component with specific F_x
|
| 104 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 105 |
+
Vx2 = F_x_specific
|
| 106 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 107 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 108 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 109 |
+
|
| 110 |
+
return cases
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 114 |
+
"""Case 2: Both components are linear functions"""
|
| 115 |
+
|
| 116 |
+
def __init__(self):
|
| 117 |
+
super().__init__()
|
| 118 |
+
# Define constants for linear functions
|
| 119 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 120 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 121 |
+
|
| 122 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 123 |
+
"""Find potential function for given linear vector field"""
|
| 124 |
+
if Vx is None or Vy is None:
|
| 125 |
+
Vx, Vy = self.get_general_linear_field()
|
| 126 |
+
|
| 127 |
+
# Use the method from the case study
|
| 128 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 129 |
+
return phi
|
| 130 |
+
|
| 131 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 132 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 133 |
+
# Method: Integrate Vx with respect to x
|
| 134 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 135 |
+
|
| 136 |
+
# Differentiate with respect to y and compare with Vy
|
| 137 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 138 |
+
remaining = Vy - diff_phi_x_y
|
| 139 |
+
|
| 140 |
+
# Integrate remaining with respect to y
|
| 141 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 142 |
+
|
| 143 |
+
# Combine results (no additional constants needed)
|
| 144 |
+
phi = phi_x + phi_y
|
| 145 |
+
|
| 146 |
+
return phi
|
| 147 |
+
|
| 148 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 149 |
+
return self.get_general_linear_field()
|
| 150 |
+
|
| 151 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 152 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 153 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 154 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 155 |
+
return Vx, Vy
|
| 156 |
+
|
| 157 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 158 |
+
"""Return the condition for the field to be a gradient field"""
|
| 159 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 160 |
+
Vx, Vy = self.get_general_linear_field()
|
| 161 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 162 |
+
return sp.simplify(condition)
|
| 163 |
+
|
| 164 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 165 |
+
"""Find potential for a specific numeric vector field using correct integration"""
|
| 166 |
+
# Use the direct integration method that works for any gradient field
|
| 167 |
+
return self._find_potential_2d(Vx, Vy)
|
| 168 |
+
|
| 169 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 170 |
+
"""Return specific cases where the field is a gradient field"""
|
| 171 |
+
cases = {}
|
| 172 |
+
|
| 173 |
+
# Case 2a: a2 = b1 (from the case study) - FORM 1
|
| 174 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 175 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 176 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 177 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 178 |
+
|
| 179 |
+
# Case 2b: b2 = a1 (alternative symmetric case) - FORM 2
|
| 180 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 181 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 182 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 183 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 184 |
+
|
| 185 |
+
return cases
|
| 186 |
+
|
| 187 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 188 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 189 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 190 |
+
return sp.simplify(condition) == 0
|
| 191 |
+
|
| 192 |
+
def demonstrate_case_study_forms(self):
|
| 193 |
+
"""Demonstrate the exact forms from the case study"""
|
| 194 |
+
print("\nEXACT FORMS FROM CASE STUDY:")
|
| 195 |
+
print("=" * 50)
|
| 196 |
+
|
| 197 |
+
# Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)
|
| 198 |
+
print("Form 1 (Equation 3.44):")
|
| 199 |
+
phi_form1 = self.a1 * self.x**2 / 2 + self.b2 * self.y**2 / 2 + self.c2 * self.y + self.x * (self.b1 * self.y + self.c1)
|
| 200 |
+
print(f"φ(x,y) = {phi_form1}")
|
| 201 |
+
|
| 202 |
+
Vx_form1 = sp.diff(phi_form1, self.x)
|
| 203 |
+
Vy_form1 = sp.diff(phi_form1, self.y)
|
| 204 |
+
print(f"F(x,y) = ∇φ = [{Vx_form1}, {Vy_form1}]")
|
| 205 |
+
print(f"Matches Form 1 from case study: {Vx_form1 == self.a1*self.x + self.b1*self.y + self.c1 and Vy_form1 == self.b1*self.x + self.b2*self.y + self.c2}")
|
| 206 |
+
print()
|
| 207 |
+
|
| 208 |
+
# Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)
|
| 209 |
+
print("Form 2 (Equation 3.44):")
|
| 210 |
+
phi_form2 = self.a2 * self.x**2 / 2 + self.b1 * self.y**2 / 2 + self.c1 * self.y + self.x * (self.a1 * self.y + self.c2)
|
| 211 |
+
print(f"φ(x,y) = {phi_form2}")
|
| 212 |
+
|
| 213 |
+
Vx_form2 = sp.diff(phi_form2, self.x)
|
| 214 |
+
Vy_form2 = sp.diff(phi_form2, self.y)
|
| 215 |
+
print(f"F(x,y) = ∇φ = [{Vx_form2}, {Vy_form2}]")
|
| 216 |
+
print(f"Matches Form 2 from case study: {Vx_form2 == self.a2*self.x + self.a1*self.y + self.c2 and Vy_form2 == self.a1*self.x + self.b1*self.y + self.c1}")
|
| 217 |
+
print()
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
class GradientFieldFactory:
|
| 221 |
+
"""Factory class to create different types of gradient fields"""
|
| 222 |
+
|
| 223 |
+
@staticmethod
|
| 224 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 225 |
+
return ConstantComponentField(component, constant)
|
| 226 |
+
|
| 227 |
+
@staticmethod
|
| 228 |
+
def create_linear_component_field():
|
| 229 |
+
return LinearComponentField()
|
| 230 |
+
|
| 231 |
+
@staticmethod
|
| 232 |
+
def analyze_all_cases():
|
| 233 |
+
"""Analyze all cases from the case study"""
|
| 234 |
+
print("=" * 60)
|
| 235 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 236 |
+
print("=" * 60)
|
| 237 |
+
|
| 238 |
+
# Case 1: Constant component
|
| 239 |
+
print("\nCASE 1: One Component is Constant")
|
| 240 |
+
print("-" * 40)
|
| 241 |
+
|
| 242 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 243 |
+
phi1 = case1_x.find_potential()
|
| 244 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 245 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 246 |
+
|
| 247 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 248 |
+
phi2 = case1_y.find_potential()
|
| 249 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 250 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 251 |
+
|
| 252 |
+
# Show specific examples
|
| 253 |
+
print("\nSpecific Examples for Case 1:")
|
| 254 |
+
examples1 = case1_x.get_specific_cases()
|
| 255 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 256 |
+
print(f"{case_name}:")
|
| 257 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 258 |
+
|
| 259 |
+
# Case 2: Linear components
|
| 260 |
+
print("\nCASE 2: Both Components are Linear")
|
| 261 |
+
print("-" * 40)
|
| 262 |
+
|
| 263 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 264 |
+
|
| 265 |
+
# Show gradient condition
|
| 266 |
+
condition = case2.get_gradient_condition()
|
| 267 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 268 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 269 |
+
|
| 270 |
+
# Show specific gradient cases
|
| 271 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 272 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 273 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 274 |
+
print(f"{case_name}:")
|
| 275 |
+
case2.display_results(phi, Vx, Vy)
|
| 276 |
+
|
| 277 |
+
# Demonstrate the exact forms from the case study
|
| 278 |
+
case2.demonstrate_case_study_forms()
|
| 279 |
+
|
| 280 |
+
return case1_x, case1_y, case2
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class NumericalExamples:
|
| 284 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 285 |
+
|
| 286 |
+
def __init__(self):
|
| 287 |
+
self.x, self.y = sp.symbols('x y')
|
| 288 |
+
self.constant_analyzer = ConstantComponentField()
|
| 289 |
+
self.linear_analyzer = LinearComponentField()
|
| 290 |
+
|
| 291 |
+
def run_case1_examples(self):
|
| 292 |
+
"""Run numerical examples for Case 1"""
|
| 293 |
+
print("\nNumerical Examples - Case 1:")
|
| 294 |
+
print("-" * 40)
|
| 295 |
+
|
| 296 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 297 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 298 |
+
Vx1 = 2
|
| 299 |
+
Vy1 = 3 * self.y**2
|
| 300 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 301 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 302 |
+
|
| 303 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 304 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 305 |
+
Vx2 = sp.sin(self.x)
|
| 306 |
+
Vy2 = 5
|
| 307 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 308 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 309 |
+
|
| 310 |
+
def run_case2_examples(self):
|
| 311 |
+
"""Run numerical examples for Case 2"""
|
| 312 |
+
print("\nNumerical Examples - Case 2:")
|
| 313 |
+
print("-" * 40)
|
| 314 |
+
|
| 315 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 316 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 317 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 318 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 319 |
+
|
| 320 |
+
# Check if it's a gradient field first
|
| 321 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 322 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 323 |
+
|
| 324 |
+
if is_gradient:
|
| 325 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 326 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 327 |
+
|
| 328 |
+
# Show that this matches Form 1 from the case study
|
| 329 |
+
print("This matches Form 1 from case study with:")
|
| 330 |
+
print(" a1 = 2, b1 = 3, c1 = 1")
|
| 331 |
+
print(" a2 = 3, b2 = 4, c2 = 2")
|
| 332 |
+
print(" φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 333 |
+
print(f" = 2x²/2 + 4y²/2 + 2y + x(3y + 1)")
|
| 334 |
+
print(f" = {phi1}")
|
| 335 |
+
else:
|
| 336 |
+
print("This field cannot have a potential function!")
|
| 337 |
+
print("-" * 50)
|
| 338 |
+
|
| 339 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 340 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 341 |
+
Vx2 = self.x + 2*self.y
|
| 342 |
+
Vy2 = 2*self.x + 3*self.y
|
| 343 |
+
|
| 344 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 345 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 346 |
+
|
| 347 |
+
if is_gradient2:
|
| 348 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 349 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 350 |
+
else:
|
| 351 |
+
print("This field cannot have a potential function!")
|
| 352 |
+
print("-" * 50)
|
| 353 |
+
|
| 354 |
+
# Example 3: Non-gradient field (should fail)
|
| 355 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 356 |
+
Vx3 = self.x + self.y
|
| 357 |
+
Vy3 = 2*self.x + self.y # ∂P/∂y = 1, ∂Q/∂x = 2 → not equal
|
| 358 |
+
|
| 359 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 360 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 361 |
+
|
| 362 |
+
if is_gradient3:
|
| 363 |
+
try:
|
| 364 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 365 |
+
self.linear_analyzer.display_results(phi3, Vx3, Vy3)
|
| 366 |
+
except Exception as e:
|
| 367 |
+
print(f"Error finding potential: {e}")
|
| 368 |
+
else:
|
| 369 |
+
print("This field cannot have a potential function!")
|
| 370 |
+
print("Reason: ∂P/∂y = 1 ≠ ∂Q/∂x = 2")
|
| 371 |
+
print("-" * 50)
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def main():
|
| 375 |
+
# Run the complete analysis
|
| 376 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 377 |
+
|
| 378 |
+
# Run numerical examples
|
| 379 |
+
print("\n" + "=" * 60)
|
| 380 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 381 |
+
print("=" * 60)
|
| 382 |
+
|
| 383 |
+
numerical_examples = NumericalExamples()
|
| 384 |
+
numerical_examples.run_case1_examples()
|
| 385 |
+
numerical_examples.run_case2_examples()
|
| 386 |
+
|
| 387 |
+
# Additional demonstration of the key insight
|
| 388 |
+
print("\n" + "=" * 60)
|
| 389 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 390 |
+
print("=" * 60)
|
| 391 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 392 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 393 |
+
print("• The potential function has two equivalent forms:")
|
| 394 |
+
print(" Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 395 |
+
print(" Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 396 |
+
print("• These match equations (3.44) and (3.45) from the case study")
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
if __name__ == "__main__":
|
| 400 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_006.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
return diff_x == 0 and diff_y == 0, diff_x, diff_y
|
| 29 |
+
|
| 30 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 31 |
+
"""Display the results in a formatted way"""
|
| 32 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 33 |
+
print(f"Potential Function: φ(x,y) = {sp.simplify(phi)}")
|
| 34 |
+
|
| 35 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 36 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 37 |
+
if not is_valid:
|
| 38 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 39 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 40 |
+
print("-" * 50)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 44 |
+
"""Case 1: One component is constant"""
|
| 45 |
+
|
| 46 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.constant_component = constant_component.lower()
|
| 49 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 50 |
+
|
| 51 |
+
# Define unknown functions
|
| 52 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 53 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 54 |
+
|
| 55 |
+
def find_potential(self) -> sp.Expr:
|
| 56 |
+
if self.constant_component == 'x':
|
| 57 |
+
# F(x,y) = [c, F_y(y)]
|
| 58 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 59 |
+
else: # constant y-component
|
| 60 |
+
# F(x,y) = [F_x(x), c]
|
| 61 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 62 |
+
|
| 63 |
+
return phi
|
| 64 |
+
|
| 65 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 66 |
+
if self.constant_component == 'x':
|
| 67 |
+
return self.c, self.F_y
|
| 68 |
+
else:
|
| 69 |
+
return self.F_x, self.c
|
| 70 |
+
|
| 71 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 72 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 73 |
+
# Check which component is constant
|
| 74 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 75 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 76 |
+
|
| 77 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 78 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 79 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 80 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 81 |
+
|
| 82 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 83 |
+
# F(x,y) = [constant, Vy]
|
| 84 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 85 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 86 |
+
# F(x,y) = [Vx, constant]
|
| 87 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 88 |
+
else:
|
| 89 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 90 |
+
return sp.simplify(phi)
|
| 91 |
+
|
| 92 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 93 |
+
"""Return specific cases with actual functions"""
|
| 94 |
+
cases = {}
|
| 95 |
+
|
| 96 |
+
# Case 1a: Constant x-component with specific F_y
|
| 97 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 98 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 99 |
+
Vy1 = F_y_specific
|
| 100 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 101 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 102 |
+
|
| 103 |
+
# Case 1b: Constant y-component with specific F_x
|
| 104 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 105 |
+
Vx2 = F_x_specific
|
| 106 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 107 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 108 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 109 |
+
|
| 110 |
+
return cases
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 114 |
+
"""Case 2: Both components are linear functions"""
|
| 115 |
+
|
| 116 |
+
def __init__(self):
|
| 117 |
+
super().__init__()
|
| 118 |
+
# Define constants for linear functions
|
| 119 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 120 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 121 |
+
|
| 122 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 123 |
+
"""Find potential function for given linear vector field"""
|
| 124 |
+
if Vx is None or Vy is None:
|
| 125 |
+
Vx, Vy = self.get_general_linear_field()
|
| 126 |
+
|
| 127 |
+
# Use the method from the case study
|
| 128 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 129 |
+
return phi
|
| 130 |
+
|
| 131 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 132 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 133 |
+
# Method: Integrate Vx with respect to x
|
| 134 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 135 |
+
|
| 136 |
+
# Differentiate with respect to y and compare with Vy
|
| 137 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 138 |
+
remaining = Vy - diff_phi_x_y
|
| 139 |
+
|
| 140 |
+
# Integrate remaining with respect to y
|
| 141 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 142 |
+
|
| 143 |
+
# Combine results (no additional constants needed)
|
| 144 |
+
phi = phi_x + phi_y
|
| 145 |
+
|
| 146 |
+
return sp.simplify(phi)
|
| 147 |
+
|
| 148 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 149 |
+
return self.get_general_linear_field()
|
| 150 |
+
|
| 151 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 152 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 153 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 154 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 155 |
+
return Vx, Vy
|
| 156 |
+
|
| 157 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 158 |
+
"""Return the condition for the field to be a gradient field"""
|
| 159 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 160 |
+
Vx, Vy = self.get_general_linear_field()
|
| 161 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 162 |
+
return sp.simplify(condition)
|
| 163 |
+
|
| 164 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 165 |
+
"""Find potential for a specific numeric vector field using correct integration"""
|
| 166 |
+
# Use the direct integration method that works for any gradient field
|
| 167 |
+
return self._find_potential_2d(Vx, Vy)
|
| 168 |
+
|
| 169 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 170 |
+
"""Return specific cases where the field is a gradient field"""
|
| 171 |
+
cases = {}
|
| 172 |
+
|
| 173 |
+
# Case 2a: a2 = b1 (from the case study) - FORM 1
|
| 174 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 175 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 176 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 177 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 178 |
+
|
| 179 |
+
# Case 2b: b2 = a1 (alternative symmetric case) - FORM 2
|
| 180 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 181 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 182 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 183 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 184 |
+
|
| 185 |
+
return cases
|
| 186 |
+
|
| 187 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 188 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 189 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 190 |
+
return sp.simplify(condition) == 0
|
| 191 |
+
|
| 192 |
+
def demonstrate_case_study_forms(self):
|
| 193 |
+
"""Demonstrate the exact forms from the case study"""
|
| 194 |
+
print("\nEXACT FORMS FROM CASE STUDY:")
|
| 195 |
+
print("=" * 50)
|
| 196 |
+
|
| 197 |
+
# Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)
|
| 198 |
+
print("Form 1 (Equation 3.44):")
|
| 199 |
+
phi_form1 = self.a1 * self.x**2 / 2 + self.b2 * self.y**2 / 2 + self.c2 * self.y + self.x * (self.b1 * self.y + self.c1)
|
| 200 |
+
print(f"φ(x,y) = {phi_form1}")
|
| 201 |
+
|
| 202 |
+
Vx_form1 = sp.diff(phi_form1, self.x)
|
| 203 |
+
Vy_form1 = sp.diff(phi_form1, self.y)
|
| 204 |
+
print(f"F(x,y) = ∇φ = [{Vx_form1}, {Vy_form1}]")
|
| 205 |
+
print(f"Matches Form 1 from case study: {Vx_form1 == self.a1*self.x + self.b1*self.y + self.c1 and Vy_form1 == self.b1*self.x + self.b2*self.y + self.c2}")
|
| 206 |
+
print()
|
| 207 |
+
|
| 208 |
+
# Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)
|
| 209 |
+
print("Form 2 (Equation 3.44):")
|
| 210 |
+
phi_form2 = self.a2 * self.x**2 / 2 + self.b1 * self.y**2 / 2 + self.c1 * self.y + self.x * (self.a1 * self.y + self.c2)
|
| 211 |
+
print(f"φ(x,y) = {phi_form2}")
|
| 212 |
+
|
| 213 |
+
Vx_form2 = sp.diff(phi_form2, self.x)
|
| 214 |
+
Vy_form2 = sp.diff(phi_form2, self.y)
|
| 215 |
+
print(f"F(x,y) = ∇φ = [{Vx_form2}, {Vy_form2}]")
|
| 216 |
+
print(f"Matches Form 2 from case study: {Vx_form2 == self.a2*self.x + self.a1*self.y + self.c2 and Vy_form2 == self.a1*self.x + self.b1*self.y + self.c1}")
|
| 217 |
+
print()
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
class GradientFieldFactory:
|
| 221 |
+
"""Factory class to create different types of gradient fields"""
|
| 222 |
+
|
| 223 |
+
@staticmethod
|
| 224 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 225 |
+
return ConstantComponentField(component, constant)
|
| 226 |
+
|
| 227 |
+
@staticmethod
|
| 228 |
+
def create_linear_component_field():
|
| 229 |
+
return LinearComponentField()
|
| 230 |
+
|
| 231 |
+
@staticmethod
|
| 232 |
+
def analyze_all_cases():
|
| 233 |
+
"""Analyze all cases from the case study"""
|
| 234 |
+
print("=" * 60)
|
| 235 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 236 |
+
print("=" * 60)
|
| 237 |
+
|
| 238 |
+
# Case 1: Constant component
|
| 239 |
+
print("\nCASE 1: One Component is Constant")
|
| 240 |
+
print("-" * 40)
|
| 241 |
+
|
| 242 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 243 |
+
phi1 = case1_x.find_potential()
|
| 244 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 245 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 246 |
+
|
| 247 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 248 |
+
phi2 = case1_y.find_potential()
|
| 249 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 250 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 251 |
+
|
| 252 |
+
# Show specific examples
|
| 253 |
+
print("\nSpecific Examples for Case 1:")
|
| 254 |
+
examples1 = case1_x.get_specific_cases()
|
| 255 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 256 |
+
print(f"{case_name}:")
|
| 257 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 258 |
+
|
| 259 |
+
# Case 2: Linear components
|
| 260 |
+
print("\nCASE 2: Both Components are Linear")
|
| 261 |
+
print("-" * 40)
|
| 262 |
+
|
| 263 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 264 |
+
|
| 265 |
+
# Show gradient condition
|
| 266 |
+
condition = case2.get_gradient_condition()
|
| 267 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 268 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 269 |
+
|
| 270 |
+
# Show specific gradient cases
|
| 271 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 272 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 273 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 274 |
+
print(f"{case_name}:")
|
| 275 |
+
case2.display_results(phi, Vx, Vy)
|
| 276 |
+
|
| 277 |
+
# Demonstrate the exact forms from the case study
|
| 278 |
+
case2.demonstrate_case_study_forms()
|
| 279 |
+
|
| 280 |
+
return case1_x, case1_y, case2
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class NumericalExamples:
|
| 284 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 285 |
+
|
| 286 |
+
def __init__(self):
|
| 287 |
+
self.x, self.y = sp.symbols('x y')
|
| 288 |
+
self.constant_analyzer = ConstantComponentField()
|
| 289 |
+
self.linear_analyzer = LinearComponentField()
|
| 290 |
+
|
| 291 |
+
def run_case1_examples(self):
|
| 292 |
+
"""Run numerical examples for Case 1"""
|
| 293 |
+
print("\nNumerical Examples - Case 1:")
|
| 294 |
+
print("-" * 40)
|
| 295 |
+
|
| 296 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 297 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 298 |
+
Vx1 = 2
|
| 299 |
+
Vy1 = 3 * self.y**2
|
| 300 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 301 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 302 |
+
|
| 303 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 304 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 305 |
+
Vx2 = sp.sin(self.x)
|
| 306 |
+
Vy2 = 5
|
| 307 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 308 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 309 |
+
|
| 310 |
+
def run_case2_examples(self):
|
| 311 |
+
"""Run numerical examples for Case 2"""
|
| 312 |
+
print("\nNumerical Examples - Case 2:")
|
| 313 |
+
print("-" * 40)
|
| 314 |
+
|
| 315 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 316 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 317 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 318 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 319 |
+
|
| 320 |
+
# Check if it's a gradient field first
|
| 321 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 322 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 323 |
+
|
| 324 |
+
if is_gradient:
|
| 325 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 326 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 327 |
+
|
| 328 |
+
# Show that this matches Form 1 from the case study
|
| 329 |
+
print("This matches Form 1 from case study with:")
|
| 330 |
+
print(" a1 = 2, b1 = 3, c1 = 1")
|
| 331 |
+
print(" a2 = 3, b2 = 4, c2 = 2")
|
| 332 |
+
phi_form1 = 2*self.x**2/2 + 4*self.y**2/2 + 2*self.y + self.x*(3*self.y + 1)
|
| 333 |
+
print(f" φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 334 |
+
print(f" = {sp.simplify(phi_form1)}")
|
| 335 |
+
else:
|
| 336 |
+
print("This field cannot have a potential function!")
|
| 337 |
+
print("-" * 50)
|
| 338 |
+
|
| 339 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 340 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 341 |
+
Vx2 = self.x + 2*self.y
|
| 342 |
+
Vy2 = 2*self.x + 3*self.y
|
| 343 |
+
|
| 344 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 345 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 346 |
+
|
| 347 |
+
if is_gradient2:
|
| 348 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 349 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 350 |
+
else:
|
| 351 |
+
print("This field cannot have a potential function!")
|
| 352 |
+
print("-" * 50)
|
| 353 |
+
|
| 354 |
+
# Example 3: Non-gradient field (should fail)
|
| 355 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 356 |
+
Vx3 = self.x + self.y
|
| 357 |
+
Vy3 = 2*self.x + self.y
|
| 358 |
+
|
| 359 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 360 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 361 |
+
|
| 362 |
+
if is_gradient3:
|
| 363 |
+
print("ERROR: This should not be a gradient field!")
|
| 364 |
+
print("Let's check the gradient condition:")
|
| 365 |
+
print(f" ∂P/∂y = {sp.diff(Vx3, self.y)}")
|
| 366 |
+
print(f" ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 367 |
+
print(f" Gradient condition satisfied? {sp.diff(Vx3, self.y) == sp.diff(Vy3, self.x)}")
|
| 368 |
+
|
| 369 |
+
# Even if the method finds something, it shouldn't verify
|
| 370 |
+
try:
|
| 371 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 372 |
+
self.linear_analyzer.display_results(phi3, Vx3, Vy3)
|
| 373 |
+
except Exception as e:
|
| 374 |
+
print(f"Error finding potential: {e}")
|
| 375 |
+
else:
|
| 376 |
+
print("✓ Correct: This field cannot have a potential function!")
|
| 377 |
+
print(f"Reason: ∂P/∂y = {sp.diff(Vx3, self.y)} ≠ ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 378 |
+
print("The gradient condition a₂ = b₁ is not satisfied")
|
| 379 |
+
print("-" * 50)
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def main():
|
| 383 |
+
# Run the complete analysis
|
| 384 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 385 |
+
|
| 386 |
+
# Run numerical examples
|
| 387 |
+
print("\n" + "=" * 60)
|
| 388 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 389 |
+
print("=" * 60)
|
| 390 |
+
|
| 391 |
+
numerical_examples = NumericalExamples()
|
| 392 |
+
numerical_examples.run_case1_examples()
|
| 393 |
+
numerical_examples.run_case2_examples()
|
| 394 |
+
|
| 395 |
+
# Additional demonstration of the key insight
|
| 396 |
+
print("\n" + "=" * 60)
|
| 397 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 398 |
+
print("=" * 60)
|
| 399 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 400 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 401 |
+
print("• The potential function has two equivalent forms:")
|
| 402 |
+
print(" Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 403 |
+
print(" Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 404 |
+
print("• These match equations (3.44) from the case study")
|
| 405 |
+
print("\nSUMMARY OF RESULTS:")
|
| 406 |
+
print("• Case 1 (constant component): Always a gradient field")
|
| 407 |
+
print("• Case 2 (linear components): Gradient field only when a₂ = b₁")
|
| 408 |
+
print("• The integration method correctly identifies gradient fields")
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
if __name__ == "__main__":
|
| 412 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_007.py
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
return diff_x == 0 and diff_y == 0, diff_x, diff_y
|
| 29 |
+
|
| 30 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 31 |
+
"""Display the results in a formatted way"""
|
| 32 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 33 |
+
print(f"Potential Function: φ(x,y) = {sp.simplify(phi)}")
|
| 34 |
+
|
| 35 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 36 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 37 |
+
if not is_valid:
|
| 38 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 39 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 40 |
+
print("-" * 50)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 44 |
+
"""Case 1: One component is constant"""
|
| 45 |
+
|
| 46 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.constant_component = constant_component.lower()
|
| 49 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 50 |
+
|
| 51 |
+
# Define unknown functions
|
| 52 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 53 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 54 |
+
|
| 55 |
+
def find_potential(self) -> sp.Expr:
|
| 56 |
+
if self.constant_component == 'x':
|
| 57 |
+
# F(x,y) = [c, F_y(y)]
|
| 58 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 59 |
+
else: # constant y-component
|
| 60 |
+
# F(x,y) = [F_x(x), c]
|
| 61 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 62 |
+
|
| 63 |
+
return phi
|
| 64 |
+
|
| 65 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 66 |
+
if self.constant_component == 'x':
|
| 67 |
+
return self.c, self.F_y
|
| 68 |
+
else:
|
| 69 |
+
return self.F_x, self.c
|
| 70 |
+
|
| 71 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 72 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 73 |
+
# Check which component is constant
|
| 74 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 75 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 76 |
+
|
| 77 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 78 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 79 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 80 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 81 |
+
|
| 82 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 83 |
+
# F(x,y) = [constant, Vy]
|
| 84 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 85 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 86 |
+
# F(x,y) = [Vx, constant]
|
| 87 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 88 |
+
else:
|
| 89 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 90 |
+
return sp.simplify(phi)
|
| 91 |
+
|
| 92 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 93 |
+
"""Return specific cases with actual functions"""
|
| 94 |
+
cases = {}
|
| 95 |
+
|
| 96 |
+
# Case 1a: Constant x-component with specific F_y
|
| 97 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 98 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 99 |
+
Vy1 = F_y_specific
|
| 100 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 101 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 102 |
+
|
| 103 |
+
# Case 1b: Constant y-component with specific F_x
|
| 104 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 105 |
+
Vx2 = F_x_specific
|
| 106 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 107 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 108 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 109 |
+
|
| 110 |
+
return cases
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 114 |
+
"""Case 2: Both components are linear functions"""
|
| 115 |
+
|
| 116 |
+
def __init__(self):
|
| 117 |
+
super().__init__()
|
| 118 |
+
# Define constants for linear functions
|
| 119 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 120 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 121 |
+
|
| 122 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 123 |
+
"""Find potential function for given linear vector field"""
|
| 124 |
+
if Vx is None or Vy is None:
|
| 125 |
+
Vx, Vy = self.get_general_linear_field()
|
| 126 |
+
|
| 127 |
+
# Use the method from the case study
|
| 128 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 129 |
+
return phi
|
| 130 |
+
|
| 131 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 132 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 133 |
+
# First check if it's a gradient field
|
| 134 |
+
if not self.check_gradient_condition_for_specific(Vx, Vy):
|
| 135 |
+
raise ValueError("Field is not a gradient field - cannot find potential")
|
| 136 |
+
|
| 137 |
+
# Method: Integrate Vx with respect to x
|
| 138 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 139 |
+
|
| 140 |
+
# Differentiate with respect to y and compare with Vy
|
| 141 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 142 |
+
remaining = Vy - diff_phi_x_y
|
| 143 |
+
|
| 144 |
+
# Integrate remaining with respect to y
|
| 145 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 146 |
+
|
| 147 |
+
# Combine results (no additional constants needed)
|
| 148 |
+
phi = phi_x + phi_y
|
| 149 |
+
|
| 150 |
+
return sp.simplify(phi)
|
| 151 |
+
|
| 152 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 153 |
+
return self.get_general_linear_field()
|
| 154 |
+
|
| 155 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 156 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 157 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 158 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 159 |
+
return Vx, Vy
|
| 160 |
+
|
| 161 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 162 |
+
"""Return the condition for the field to be a gradient field"""
|
| 163 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 164 |
+
Vx, Vy = self.get_general_linear_field()
|
| 165 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 166 |
+
return sp.simplify(condition)
|
| 167 |
+
|
| 168 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 169 |
+
"""Find potential for a specific numeric vector field using correct integration"""
|
| 170 |
+
# Use the direct integration method that works for any gradient field
|
| 171 |
+
return self._find_potential_2d(Vx, Vy)
|
| 172 |
+
|
| 173 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 174 |
+
"""Return specific cases where the field is a gradient field"""
|
| 175 |
+
cases = {}
|
| 176 |
+
|
| 177 |
+
# Case 2a: a2 = b1 (from the case study) - FORM 1
|
| 178 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 179 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 180 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 181 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 182 |
+
|
| 183 |
+
# Case 2b: b2 = a1 (alternative symmetric case) - FORM 2
|
| 184 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 185 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 186 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 187 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 188 |
+
|
| 189 |
+
return cases
|
| 190 |
+
|
| 191 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 192 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 193 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 194 |
+
return sp.simplify(condition) == 0
|
| 195 |
+
|
| 196 |
+
def demonstrate_case_study_forms(self):
|
| 197 |
+
"""Demonstrate the exact forms from the case study"""
|
| 198 |
+
print("\nEXACT FORMS FROM CASE STUDY:")
|
| 199 |
+
print("=" * 50)
|
| 200 |
+
|
| 201 |
+
# Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)
|
| 202 |
+
print("Form 1 (Equation 3.44):")
|
| 203 |
+
phi_form1 = self.a1 * self.x**2 / 2 + self.b2 * self.y**2 / 2 + self.c2 * self.y + self.x * (self.b1 * self.y + self.c1)
|
| 204 |
+
print(f"φ(x,y) = {phi_form1}")
|
| 205 |
+
|
| 206 |
+
Vx_form1 = sp.diff(phi_form1, self.x)
|
| 207 |
+
Vy_form1 = sp.diff(phi_form1, self.y)
|
| 208 |
+
print(f"F(x,y) = ∇φ = [{Vx_form1}, {Vy_form1}]")
|
| 209 |
+
print(f"Matches Form 1 from case study: {Vx_form1 == self.a1*self.x + self.b1*self.y + self.c1 and Vy_form1 == self.b1*self.x + self.b2*self.y + self.c2}")
|
| 210 |
+
print()
|
| 211 |
+
|
| 212 |
+
# Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)
|
| 213 |
+
print("Form 2 (Equation 3.44):")
|
| 214 |
+
phi_form2 = self.a2 * self.x**2 / 2 + self.b1 * self.y**2 / 2 + self.c1 * self.y + self.x * (self.a1 * self.y + self.c2)
|
| 215 |
+
print(f"φ(x,y) = {phi_form2}")
|
| 216 |
+
|
| 217 |
+
Vx_form2 = sp.diff(phi_form2, self.x)
|
| 218 |
+
Vy_form2 = sp.diff(phi_form2, self.y)
|
| 219 |
+
print(f"F(x,y) = ∇φ = [{Vx_form2}, {Vy_form2}]")
|
| 220 |
+
print(f"Matches Form 2 from case study: {Vx_form2 == self.a2*self.x + self.a1*self.y + self.c2 and Vy_form2 == self.a1*self.x + self.b1*self.y + self.c1}")
|
| 221 |
+
print()
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class GradientFieldFactory:
|
| 225 |
+
"""Factory class to create different types of gradient fields"""
|
| 226 |
+
|
| 227 |
+
@staticmethod
|
| 228 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 229 |
+
return ConstantComponentField(component, constant)
|
| 230 |
+
|
| 231 |
+
@staticmethod
|
| 232 |
+
def create_linear_component_field():
|
| 233 |
+
return LinearComponentField()
|
| 234 |
+
|
| 235 |
+
@staticmethod
|
| 236 |
+
def analyze_all_cases():
|
| 237 |
+
"""Analyze all cases from the case study"""
|
| 238 |
+
print("=" * 60)
|
| 239 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 240 |
+
print("=" * 60)
|
| 241 |
+
|
| 242 |
+
# Case 1: Constant component
|
| 243 |
+
print("\nCASE 1: One Component is Constant")
|
| 244 |
+
print("-" * 40)
|
| 245 |
+
|
| 246 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 247 |
+
phi1 = case1_x.find_potential()
|
| 248 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 249 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 250 |
+
|
| 251 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 252 |
+
phi2 = case1_y.find_potential()
|
| 253 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 254 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 255 |
+
|
| 256 |
+
# Show specific examples
|
| 257 |
+
print("\nSpecific Examples for Case 1:")
|
| 258 |
+
examples1 = case1_x.get_specific_cases()
|
| 259 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 260 |
+
print(f"{case_name}:")
|
| 261 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 262 |
+
|
| 263 |
+
# Case 2: Linear components
|
| 264 |
+
print("\nCASE 2: Both Components are Linear")
|
| 265 |
+
print("-" * 40)
|
| 266 |
+
|
| 267 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 268 |
+
|
| 269 |
+
# Show gradient condition
|
| 270 |
+
condition = case2.get_gradient_condition()
|
| 271 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 272 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 273 |
+
|
| 274 |
+
# Show specific gradient cases
|
| 275 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 276 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 277 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 278 |
+
print(f"{case_name}:")
|
| 279 |
+
case2.display_results(phi, Vx, Vy)
|
| 280 |
+
|
| 281 |
+
# Demonstrate the exact forms from the case study
|
| 282 |
+
case2.demonstrate_case_study_forms()
|
| 283 |
+
|
| 284 |
+
return case1_x, case1_y, case2
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
class NumericalExamples:
|
| 288 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 289 |
+
|
| 290 |
+
def __init__(self):
|
| 291 |
+
self.x, self.y = sp.symbols('x y')
|
| 292 |
+
self.constant_analyzer = ConstantComponentField()
|
| 293 |
+
self.linear_analyzer = LinearComponentField()
|
| 294 |
+
|
| 295 |
+
def run_case1_examples(self):
|
| 296 |
+
"""Run numerical examples for Case 1"""
|
| 297 |
+
print("\nNumerical Examples - Case 1:")
|
| 298 |
+
print("-" * 40)
|
| 299 |
+
|
| 300 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 301 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 302 |
+
Vx1 = 2
|
| 303 |
+
Vy1 = 3 * self.y**2
|
| 304 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 305 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 306 |
+
|
| 307 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 308 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 309 |
+
Vx2 = sp.sin(self.x)
|
| 310 |
+
Vy2 = 5
|
| 311 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 312 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 313 |
+
|
| 314 |
+
def run_case2_examples(self):
|
| 315 |
+
"""Run numerical examples for Case 2"""
|
| 316 |
+
print("\nNumerical Examples - Case 2:")
|
| 317 |
+
print("-" * 40)
|
| 318 |
+
|
| 319 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 320 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 321 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 322 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 323 |
+
|
| 324 |
+
# Check if it's a gradient field first
|
| 325 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 326 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 327 |
+
|
| 328 |
+
if is_gradient:
|
| 329 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 330 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 331 |
+
|
| 332 |
+
# Show that this matches Form 1 from the case study
|
| 333 |
+
print("This matches Form 1 from case study with:")
|
| 334 |
+
print(" a1 = 2, b1 = 3, c1 = 1")
|
| 335 |
+
print(" a2 = 3, b2 = 4, c2 = 2")
|
| 336 |
+
phi_form1 = 2*self.x**2/2 + 4*self.y**2/2 + 2*self.y + self.x*(3*self.y + 1)
|
| 337 |
+
print(f" φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 338 |
+
print(f" = {sp.simplify(phi_form1)}")
|
| 339 |
+
else:
|
| 340 |
+
print("This field cannot have a potential function!")
|
| 341 |
+
print("-" * 50)
|
| 342 |
+
|
| 343 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 344 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 345 |
+
Vx2 = self.x + 2*self.y
|
| 346 |
+
Vy2 = 2*self.x + 3*self.y
|
| 347 |
+
|
| 348 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 349 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 350 |
+
|
| 351 |
+
if is_gradient2:
|
| 352 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 353 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 354 |
+
else:
|
| 355 |
+
print("This field cannot have a potential function!")
|
| 356 |
+
print("-" * 50)
|
| 357 |
+
|
| 358 |
+
# Example 3: Non-gradient field (should fail)
|
| 359 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 360 |
+
Vx3 = self.x + self.y
|
| 361 |
+
Vy3 = 2*self.x + self.y
|
| 362 |
+
|
| 363 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 364 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 365 |
+
|
| 366 |
+
if is_gradient3:
|
| 367 |
+
print("ERROR: This should not be a gradient field!")
|
| 368 |
+
print("Let's check the gradient condition:")
|
| 369 |
+
print(f" ∂P/∂y = {sp.diff(Vx3, self.y)}")
|
| 370 |
+
print(f" ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 371 |
+
print(f" Gradient condition satisfied? {sp.diff(Vx3, self.y) == sp.diff(Vy3, self.x)}")
|
| 372 |
+
|
| 373 |
+
# Even if the method finds something, it shouldn't verify
|
| 374 |
+
try:
|
| 375 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 376 |
+
is_valid, diff_x, diff_y = self.linear_analyzer.verify_potential(phi3, Vx3, Vy3)
|
| 377 |
+
print(f"Attempted potential: φ(x,y) = {phi3}")
|
| 378 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 379 |
+
if not is_valid:
|
| 380 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 381 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 382 |
+
except ValueError as e:
|
| 383 |
+
print(f"✓ Correct: {e}")
|
| 384 |
+
else:
|
| 385 |
+
print("✓ Correct: This field cannot have a potential function!")
|
| 386 |
+
print(f"Reason: ∂P/∂y = {sp.diff(Vx3, self.y)} ≠ ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 387 |
+
print("The gradient condition a₂ = b₁ is not satisfied")
|
| 388 |
+
print("-" * 50)
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
def main():
|
| 392 |
+
# Run the complete analysis
|
| 393 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 394 |
+
|
| 395 |
+
# Run numerical examples
|
| 396 |
+
print("\n" + "=" * 60)
|
| 397 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 398 |
+
print("=" * 60)
|
| 399 |
+
|
| 400 |
+
numerical_examples = NumericalExamples()
|
| 401 |
+
numerical_examples.run_case1_examples()
|
| 402 |
+
numerical_examples.run_case2_examples()
|
| 403 |
+
|
| 404 |
+
# Additional demonstration of the key insight
|
| 405 |
+
print("\n" + "=" * 60)
|
| 406 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 407 |
+
print("=" * 60)
|
| 408 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 409 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 410 |
+
print("• The potential function has two equivalent forms:")
|
| 411 |
+
print(" Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 412 |
+
print(" Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 413 |
+
print("• These match equations (3.44) from the case study")
|
| 414 |
+
print("\nSUMMARY OF RESULTS:")
|
| 415 |
+
print("• Case 1 (constant component): Always a gradient field")
|
| 416 |
+
print("• Case 2 (linear components): Gradient field only when a₂ = b₁")
|
| 417 |
+
print("• The integration method correctly identifies gradient fields")
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
if __name__ == "__main__":
|
| 421 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_008.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
# Use equals() method for robust comparison instead of ==
|
| 26 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 27 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 28 |
+
|
| 29 |
+
is_valid = diff_x.equals(0) and diff_y.equals(0)
|
| 30 |
+
return is_valid, diff_x, diff_y
|
| 31 |
+
|
| 32 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 33 |
+
"""Display the results in a formatted way"""
|
| 34 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 35 |
+
print(f"Potential Function: φ(x,y) = {sp.simplify(phi)}")
|
| 36 |
+
|
| 37 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 38 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 39 |
+
if not is_valid:
|
| 40 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 41 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 42 |
+
print("-" * 50)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 46 |
+
"""Case 1: One component is constant"""
|
| 47 |
+
|
| 48 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 49 |
+
super().__init__()
|
| 50 |
+
self.constant_component = constant_component.lower()
|
| 51 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 52 |
+
|
| 53 |
+
# Define unknown functions
|
| 54 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 55 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 56 |
+
|
| 57 |
+
def find_potential(self) -> sp.Expr:
|
| 58 |
+
if self.constant_component == 'x':
|
| 59 |
+
# F(x,y) = [c, F_y(y)]
|
| 60 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 61 |
+
else: # constant y-component
|
| 62 |
+
# F(x,y) = [F_x(x), c]
|
| 63 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 64 |
+
|
| 65 |
+
return phi
|
| 66 |
+
|
| 67 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 68 |
+
if self.constant_component == 'x':
|
| 69 |
+
return self.c, self.F_y
|
| 70 |
+
else:
|
| 71 |
+
return self.F_x, self.c
|
| 72 |
+
|
| 73 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 74 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 75 |
+
# Check which component is constant
|
| 76 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 77 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 78 |
+
|
| 79 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 80 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 81 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 82 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 83 |
+
|
| 84 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 85 |
+
# F(x,y) = [constant, Vy]
|
| 86 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 87 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 88 |
+
# F(x,y) = [Vx, constant]
|
| 89 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 90 |
+
else:
|
| 91 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 92 |
+
return sp.simplify(phi)
|
| 93 |
+
|
| 94 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 95 |
+
"""Return specific cases with actual functions"""
|
| 96 |
+
cases = {}
|
| 97 |
+
|
| 98 |
+
# Case 1a: Constant x-component with specific F_y
|
| 99 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 100 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 101 |
+
Vy1 = F_y_specific
|
| 102 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 103 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 104 |
+
|
| 105 |
+
# Case 1b: Constant y-component with specific F_x
|
| 106 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 107 |
+
Vx2 = F_x_specific
|
| 108 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 109 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 110 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 111 |
+
|
| 112 |
+
return cases
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 116 |
+
"""Case 2: Both components are linear functions"""
|
| 117 |
+
|
| 118 |
+
def __init__(self):
|
| 119 |
+
super().__init__()
|
| 120 |
+
# Define constants for linear functions
|
| 121 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 122 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 123 |
+
|
| 124 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 125 |
+
"""Find potential function for given linear vector field"""
|
| 126 |
+
if Vx is None or Vy is None:
|
| 127 |
+
Vx, Vy = self.get_general_linear_field()
|
| 128 |
+
|
| 129 |
+
# Use the method from the case study
|
| 130 |
+
phi = self._find_potential_2d(Vx, Vy)
|
| 131 |
+
return phi
|
| 132 |
+
|
| 133 |
+
def _find_potential_2d(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 134 |
+
"""Implementation of find_potential_f2D from the case study"""
|
| 135 |
+
# First check if it's a gradient field
|
| 136 |
+
if not self.check_gradient_condition_for_specific(Vx, Vy):
|
| 137 |
+
raise ValueError("Field is not a gradient field - cannot find potential")
|
| 138 |
+
|
| 139 |
+
# Method: Integrate Vx with respect to x
|
| 140 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 141 |
+
|
| 142 |
+
# Differentiate with respect to y and compare with Vy
|
| 143 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 144 |
+
remaining = Vy - diff_phi_x_y
|
| 145 |
+
|
| 146 |
+
# Integrate remaining with respect to y
|
| 147 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 148 |
+
|
| 149 |
+
# Combine results (no additional constants needed)
|
| 150 |
+
phi = phi_x + phi_y
|
| 151 |
+
|
| 152 |
+
return sp.simplify(phi)
|
| 153 |
+
|
| 154 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 155 |
+
return self.get_general_linear_field()
|
| 156 |
+
|
| 157 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 158 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 159 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 160 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 161 |
+
return Vx, Vy
|
| 162 |
+
|
| 163 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 164 |
+
"""Return the condition for the field to be a gradient field"""
|
| 165 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 166 |
+
Vx, Vy = self.get_general_linear_field()
|
| 167 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 168 |
+
return sp.simplify(condition)
|
| 169 |
+
|
| 170 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 171 |
+
"""Find potential for a specific numeric vector field using correct integration"""
|
| 172 |
+
# Use the direct integration method that works for any gradient field
|
| 173 |
+
return self._find_potential_2d(Vx, Vy)
|
| 174 |
+
|
| 175 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 176 |
+
"""Return specific cases where the field is a gradient field"""
|
| 177 |
+
cases = {}
|
| 178 |
+
|
| 179 |
+
# Case 2a: a2 = b1 (from the case study) - FORM 1
|
| 180 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 181 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 182 |
+
phi1 = self._find_potential_2d(Vx1, Vy1)
|
| 183 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 184 |
+
|
| 185 |
+
# Case 2b: b2 = a1 (alternative symmetric case) - FORM 2
|
| 186 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 187 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 188 |
+
phi2 = self._find_potential_2d(Vx2, Vy2)
|
| 189 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 190 |
+
|
| 191 |
+
return cases
|
| 192 |
+
|
| 193 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 194 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 195 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 196 |
+
return sp.simplify(condition).equals(0)
|
| 197 |
+
|
| 198 |
+
def demonstrate_case_study_forms(self):
|
| 199 |
+
"""Demonstrate the exact forms from the case study"""
|
| 200 |
+
print("\nEXACT FORMS FROM CASE STUDY:")
|
| 201 |
+
print("=" * 50)
|
| 202 |
+
|
| 203 |
+
# Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)
|
| 204 |
+
print("Form 1 (Equation 3.44):")
|
| 205 |
+
phi_form1 = self.a1 * self.x**2 / 2 + self.b2 * self.y**2 / 2 + self.c2 * self.y + self.x * (self.b1 * self.y + self.c1)
|
| 206 |
+
print(f"φ(x,y) = {phi_form1}")
|
| 207 |
+
|
| 208 |
+
Vx_form1 = sp.diff(phi_form1, self.x)
|
| 209 |
+
Vy_form1 = sp.diff(phi_form1, self.y)
|
| 210 |
+
print(f"F(x,y) = ∇φ = [{Vx_form1}, {Vy_form1}]")
|
| 211 |
+
matches_form1 = Vx_form1.equals(self.a1*self.x + self.b1*self.y + self.c1) and Vy_form1.equals(self.b1*self.x + self.b2*self.y + self.c2)
|
| 212 |
+
print(f"Matches Form 1 from case study: {matches_form1}")
|
| 213 |
+
print()
|
| 214 |
+
|
| 215 |
+
# Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)
|
| 216 |
+
print("Form 2 (Equation 3.44):")
|
| 217 |
+
phi_form2 = self.a2 * self.x**2 / 2 + self.b1 * self.y**2 / 2 + self.c1 * self.y + self.x * (self.a1 * self.y + self.c2)
|
| 218 |
+
print(f"φ(x,y) = {phi_form2}")
|
| 219 |
+
|
| 220 |
+
Vx_form2 = sp.diff(phi_form2, self.x)
|
| 221 |
+
Vy_form2 = sp.diff(phi_form2, self.y)
|
| 222 |
+
print(f"F(x,y) = ∇φ = [{Vx_form2}, {Vy_form2}]")
|
| 223 |
+
matches_form2 = Vx_form2.equals(self.a2*self.x + self.a1*self.y + self.c2) and Vy_form2.equals(self.a1*self.x + self.b1*self.y + self.c1)
|
| 224 |
+
print(f"Matches Form 2 from case study: {matches_form2}")
|
| 225 |
+
print()
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
class GradientFieldFactory:
|
| 229 |
+
"""Factory class to create different types of gradient fields"""
|
| 230 |
+
|
| 231 |
+
@staticmethod
|
| 232 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 233 |
+
return ConstantComponentField(component, constant)
|
| 234 |
+
|
| 235 |
+
@staticmethod
|
| 236 |
+
def create_linear_component_field():
|
| 237 |
+
return LinearComponentField()
|
| 238 |
+
|
| 239 |
+
@staticmethod
|
| 240 |
+
def analyze_all_cases():
|
| 241 |
+
"""Analyze all cases from the case study"""
|
| 242 |
+
print("=" * 60)
|
| 243 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 244 |
+
print("=" * 60)
|
| 245 |
+
|
| 246 |
+
# Case 1: Constant component
|
| 247 |
+
print("\nCASE 1: One Component is Constant")
|
| 248 |
+
print("-" * 40)
|
| 249 |
+
|
| 250 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 251 |
+
phi1 = case1_x.find_potential()
|
| 252 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 253 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 254 |
+
|
| 255 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 256 |
+
phi2 = case1_y.find_potential()
|
| 257 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 258 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 259 |
+
|
| 260 |
+
# Show specific examples
|
| 261 |
+
print("\nSpecific Examples for Case 1:")
|
| 262 |
+
examples1 = case1_x.get_specific_cases()
|
| 263 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 264 |
+
print(f"{case_name}:")
|
| 265 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 266 |
+
|
| 267 |
+
# Case 2: Linear components
|
| 268 |
+
print("\nCASE 2: Both Components are Linear")
|
| 269 |
+
print("-" * 40)
|
| 270 |
+
|
| 271 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 272 |
+
|
| 273 |
+
# Show gradient condition
|
| 274 |
+
condition = case2.get_gradient_condition()
|
| 275 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 276 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 277 |
+
|
| 278 |
+
# Show specific gradient cases
|
| 279 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 280 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 281 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 282 |
+
print(f"{case_name}:")
|
| 283 |
+
case2.display_results(phi, Vx, Vy)
|
| 284 |
+
|
| 285 |
+
# Demonstrate the exact forms from the case study
|
| 286 |
+
case2.demonstrate_case_study_forms()
|
| 287 |
+
|
| 288 |
+
return case1_x, case1_y, case2
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
class NumericalExamples:
|
| 292 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 293 |
+
|
| 294 |
+
def __init__(self):
|
| 295 |
+
self.x, self.y = sp.symbols('x y')
|
| 296 |
+
self.constant_analyzer = ConstantComponentField()
|
| 297 |
+
self.linear_analyzer = LinearComponentField()
|
| 298 |
+
|
| 299 |
+
def run_case1_examples(self):
|
| 300 |
+
"""Run numerical examples for Case 1"""
|
| 301 |
+
print("\nNumerical Examples - Case 1:")
|
| 302 |
+
print("-" * 40)
|
| 303 |
+
|
| 304 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 305 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 306 |
+
Vx1 = 2
|
| 307 |
+
Vy1 = 3 * self.y**2
|
| 308 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 309 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 310 |
+
|
| 311 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 312 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 313 |
+
Vx2 = sp.sin(self.x)
|
| 314 |
+
Vy2 = 5
|
| 315 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 316 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 317 |
+
|
| 318 |
+
def run_case2_examples(self):
|
| 319 |
+
"""Run numerical examples for Case 2"""
|
| 320 |
+
print("\nNumerical Examples - Case 2:")
|
| 321 |
+
print("-" * 40)
|
| 322 |
+
|
| 323 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 324 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 325 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 326 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 327 |
+
|
| 328 |
+
# Check if it's a gradient field first
|
| 329 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 330 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 331 |
+
|
| 332 |
+
if is_gradient:
|
| 333 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 334 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 335 |
+
|
| 336 |
+
# Show that this matches Form 1 from the case study
|
| 337 |
+
print("This matches Form 1 from case study with:")
|
| 338 |
+
print(" a1 = 2, b1 = 3, c1 = 1")
|
| 339 |
+
print(" a2 = 3, b2 = 4, c2 = 2")
|
| 340 |
+
phi_form1 = 2*self.x**2/2 + 4*self.y**2/2 + 2*self.y + self.x*(3*self.y + 1)
|
| 341 |
+
print(f" φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 342 |
+
print(f" = {sp.simplify(phi_form1)}")
|
| 343 |
+
else:
|
| 344 |
+
print("This field cannot have a potential function!")
|
| 345 |
+
print("-" * 50)
|
| 346 |
+
|
| 347 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 348 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 349 |
+
Vx2 = self.x + 2*self.y
|
| 350 |
+
Vy2 = 2*self.x + 3*self.y
|
| 351 |
+
|
| 352 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 353 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 354 |
+
|
| 355 |
+
if is_gradient2:
|
| 356 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 357 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 358 |
+
else:
|
| 359 |
+
print("This field cannot have a potential function!")
|
| 360 |
+
print("-" * 50)
|
| 361 |
+
|
| 362 |
+
# Example 3: Non-gradient field (should fail)
|
| 363 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 364 |
+
Vx3 = self.x + self.y
|
| 365 |
+
Vy3 = 2*self.x + self.y
|
| 366 |
+
|
| 367 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 368 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 369 |
+
|
| 370 |
+
if is_gradient3:
|
| 371 |
+
print("ERROR: This should not be a gradient field!")
|
| 372 |
+
print("Let's check the gradient condition:")
|
| 373 |
+
print(f" ∂P/∂y = {sp.diff(Vx3, self.y)}")
|
| 374 |
+
print(f" ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 375 |
+
print(f" Gradient condition satisfied? {sp.diff(Vx3, self.y).equals(sp.diff(Vy3, self.x))}")
|
| 376 |
+
|
| 377 |
+
# Try to find a potential anyway to see what happens
|
| 378 |
+
try:
|
| 379 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 380 |
+
is_valid, diff_x, diff_y = self.linear_analyzer.verify_potential(phi3, Vx3, Vy3)
|
| 381 |
+
print(f"Attempted potential: φ(x,y) = {phi3}")
|
| 382 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 383 |
+
if not is_valid:
|
| 384 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 385 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 386 |
+
except ValueError as e:
|
| 387 |
+
print(f"✓ Correct: {e}")
|
| 388 |
+
else:
|
| 389 |
+
print("✓ Correct: This field cannot have a potential function!")
|
| 390 |
+
print(f"Reason: ∂P/∂y = {sp.diff(Vx3, self.y)} ≠ ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 391 |
+
print("The gradient condition a₂ = b₁ is not satisfied")
|
| 392 |
+
|
| 393 |
+
# Let's manually verify what happens if we try the integration method
|
| 394 |
+
print("\nManual verification of integration method:")
|
| 395 |
+
phi_x_manual = sp.integrate(Vx3, self.x) # ∫(x + y)dx = x²/2 + xy
|
| 396 |
+
diff_phi_x_y = sp.diff(phi_x_manual, self.y) # ∂/∂y(x²/2 + xy) = x
|
| 397 |
+
remaining = Vy3 - diff_phi_x_y # (2x + y) - x = x + y
|
| 398 |
+
phi_y_manual = sp.integrate(remaining, self.y) # ∫(x + y)dy = xy + y²/2
|
| 399 |
+
phi_manual = phi_x_manual + phi_y_manual # x²/2 + xy + xy + y²/2 = x²/2 + 2xy + y²/2
|
| 400 |
+
print(f" φ_x = ∫Vx dx = {phi_x_manual}")
|
| 401 |
+
print(f" ∂φ_x/∂y = {diff_phi_x_y}")
|
| 402 |
+
print(f" remaining = Vy - ∂φ_x/∂y = {remaining}")
|
| 403 |
+
print(f" φ_y = ∫remaining dy = {phi_y_manual}")
|
| 404 |
+
print(f" φ = φ_x + φ_y = {phi_manual}")
|
| 405 |
+
|
| 406 |
+
# Verify this "potential"
|
| 407 |
+
grad_x = sp.diff(phi_manual, self.x) # x + 2y
|
| 408 |
+
grad_y = sp.diff(phi_manual, self.y) # 2x + y
|
| 409 |
+
print(f" ∇φ = [{grad_x}, {grad_y}]")
|
| 410 |
+
print(f" ∇φ = F? {grad_x.equals(Vx3) and grad_y.equals(Vy3)}")
|
| 411 |
+
print(f" This actually works! But wait, let's check the original field:")
|
| 412 |
+
print(f" Original F = [{Vx3}, {Vy3}]")
|
| 413 |
+
print(" The issue is that for F(x,y) = [x + y, 2x + y]:")
|
| 414 |
+
print(" ∂P/∂y = 1, but ∂Q/∂x = 2")
|
| 415 |
+
print(" So this field is NOT conservative, yet we found a 'potential'")
|
| 416 |
+
print(" This reveals a bug in our integration method!")
|
| 417 |
+
print("-" * 50)
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def main():
|
| 421 |
+
# Run the complete analysis
|
| 422 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 423 |
+
|
| 424 |
+
# Run numerical examples
|
| 425 |
+
print("\n" + "=" * 60)
|
| 426 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 427 |
+
print("=" * 60)
|
| 428 |
+
|
| 429 |
+
numerical_examples = NumericalExamples()
|
| 430 |
+
numerical_examples.run_case1_examples()
|
| 431 |
+
numerical_examples.run_case2_examples()
|
| 432 |
+
|
| 433 |
+
# Additional demonstration of the key insight
|
| 434 |
+
print("\n" + "=" * 60)
|
| 435 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 436 |
+
print("=" * 60)
|
| 437 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 438 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 439 |
+
print("• The potential function has two equivalent forms:")
|
| 440 |
+
print(" Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 441 |
+
print(" Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 442 |
+
print("• These match equations (3.44) from the case study")
|
| 443 |
+
print("\nSUMMARY OF RESULTS:")
|
| 444 |
+
print("• Case 1 (constant component): Always a gradient field")
|
| 445 |
+
print("• Case 2 (linear components): Gradient field only when a₂ = b₁")
|
| 446 |
+
print("• IMPORTANT: The integration method has a bug - it can find 'potentials' for")
|
| 447 |
+
print(" non-gradient fields. Always check the gradient condition first!")
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
if __name__ == "__main__":
|
| 451 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_009.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
is_valid = diff_x.equals(0) and diff_y.equals(0)
|
| 29 |
+
return is_valid, diff_x, diff_y
|
| 30 |
+
|
| 31 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 32 |
+
"""Display the results in a formatted way"""
|
| 33 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 34 |
+
print(f"Potential Function: φ(x,y) = {sp.simplify(phi)}")
|
| 35 |
+
|
| 36 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 37 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 38 |
+
if not is_valid:
|
| 39 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 40 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 41 |
+
print("-" * 50)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 45 |
+
"""Case 1: One component is constant"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 48 |
+
super().__init__()
|
| 49 |
+
self.constant_component = constant_component.lower()
|
| 50 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 51 |
+
|
| 52 |
+
# Define unknown functions
|
| 53 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 54 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 55 |
+
|
| 56 |
+
def find_potential(self) -> sp.Expr:
|
| 57 |
+
if self.constant_component == 'x':
|
| 58 |
+
# F(x,y) = [c, F_y(y)]
|
| 59 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 60 |
+
else: # constant y-component
|
| 61 |
+
# F(x,y) = [F_x(x), c]
|
| 62 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 63 |
+
|
| 64 |
+
return phi
|
| 65 |
+
|
| 66 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 67 |
+
if self.constant_component == 'x':
|
| 68 |
+
return self.c, self.F_y
|
| 69 |
+
else:
|
| 70 |
+
return self.F_x, self.c
|
| 71 |
+
|
| 72 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 73 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 74 |
+
# Check which component is constant
|
| 75 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 76 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 77 |
+
|
| 78 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 79 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 80 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 81 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 82 |
+
|
| 83 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 84 |
+
# F(x,y) = [constant, Vy]
|
| 85 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 86 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 87 |
+
# F(x,y) = [Vx, constant]
|
| 88 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 89 |
+
else:
|
| 90 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 91 |
+
return sp.simplify(phi)
|
| 92 |
+
|
| 93 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 94 |
+
"""Return specific cases with actual functions"""
|
| 95 |
+
cases = {}
|
| 96 |
+
|
| 97 |
+
# Case 1a: Constant x-component with specific F_y
|
| 98 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 99 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 100 |
+
Vy1 = F_y_specific
|
| 101 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 102 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 103 |
+
|
| 104 |
+
# Case 1b: Constant y-component with specific F_x
|
| 105 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 106 |
+
Vx2 = F_x_specific
|
| 107 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 108 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 109 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 110 |
+
|
| 111 |
+
return cases
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 115 |
+
"""Case 2: Both components are linear functions"""
|
| 116 |
+
|
| 117 |
+
def __init__(self):
|
| 118 |
+
super().__init__()
|
| 119 |
+
# Define constants for linear functions
|
| 120 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 121 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 122 |
+
|
| 123 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 124 |
+
"""Find potential function for given linear vector field"""
|
| 125 |
+
if Vx is None or Vy is None:
|
| 126 |
+
Vx, Vy = self.get_general_linear_field()
|
| 127 |
+
|
| 128 |
+
# Use the robust method
|
| 129 |
+
phi = self._find_potential_robust(Vx, Vy)
|
| 130 |
+
return phi
|
| 131 |
+
|
| 132 |
+
def _find_potential_robust(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 133 |
+
"""Robust implementation that properly checks gradient condition"""
|
| 134 |
+
# First check if it's a gradient field
|
| 135 |
+
if not self.check_gradient_condition_for_specific(Vx, Vy):
|
| 136 |
+
raise ValueError("Field is not a gradient field - cannot find potential")
|
| 137 |
+
|
| 138 |
+
# Method: Integrate Vx with respect to x
|
| 139 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 140 |
+
|
| 141 |
+
# Differentiate with respect to y and compare with Vy
|
| 142 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 143 |
+
remaining = Vy - diff_phi_x_y
|
| 144 |
+
|
| 145 |
+
# CRITICAL: Check if remaining depends only on y
|
| 146 |
+
remaining_free_symbols = remaining.free_symbols if hasattr(remaining, 'free_symbols') else set()
|
| 147 |
+
if self.x in remaining_free_symbols:
|
| 148 |
+
raise ValueError(f"Field is not a gradient field - remaining term depends on x: {remaining}")
|
| 149 |
+
|
| 150 |
+
# Integrate remaining with respect to y
|
| 151 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 152 |
+
|
| 153 |
+
# Combine results
|
| 154 |
+
phi = phi_x + phi_y
|
| 155 |
+
|
| 156 |
+
return sp.simplify(phi)
|
| 157 |
+
|
| 158 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 159 |
+
return self.get_general_linear_field()
|
| 160 |
+
|
| 161 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 162 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 163 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 164 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 165 |
+
return Vx, Vy
|
| 166 |
+
|
| 167 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 168 |
+
"""Return the condition for the field to be a gradient field"""
|
| 169 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 170 |
+
Vx, Vy = self.get_general_linear_field()
|
| 171 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 172 |
+
return sp.simplify(condition)
|
| 173 |
+
|
| 174 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 175 |
+
"""Find potential for a specific numeric vector field using robust integration"""
|
| 176 |
+
return self._find_potential_robust(Vx, Vy)
|
| 177 |
+
|
| 178 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 179 |
+
"""Return specific cases where the field is a gradient field"""
|
| 180 |
+
cases = {}
|
| 181 |
+
|
| 182 |
+
# Case 2a: a2 = b1 (from the case study) - FORM 1
|
| 183 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 184 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 185 |
+
phi1 = self._find_potential_robust(Vx1, Vy1)
|
| 186 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 187 |
+
|
| 188 |
+
# Case 2b: b2 = a1 (alternative symmetric case) - FORM 2
|
| 189 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 190 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 191 |
+
phi2 = self._find_potential_robust(Vx2, Vy2)
|
| 192 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 193 |
+
|
| 194 |
+
return cases
|
| 195 |
+
|
| 196 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 197 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 198 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 199 |
+
return sp.simplify(condition).equals(0)
|
| 200 |
+
|
| 201 |
+
def demonstrate_case_study_forms(self):
|
| 202 |
+
"""Demonstrate the exact forms from the case study"""
|
| 203 |
+
print("\nEXACT FORMS FROM CASE STUDY:")
|
| 204 |
+
print("=" * 50)
|
| 205 |
+
|
| 206 |
+
# Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)
|
| 207 |
+
print("Form 1 (Equation 3.44):")
|
| 208 |
+
phi_form1 = self.a1 * self.x**2 / 2 + self.b2 * self.y**2 / 2 + self.c2 * self.y + self.x * (self.b1 * self.y + self.c1)
|
| 209 |
+
print(f"φ(x,y) = {phi_form1}")
|
| 210 |
+
|
| 211 |
+
Vx_form1 = sp.diff(phi_form1, self.x)
|
| 212 |
+
Vy_form1 = sp.diff(phi_form1, self.y)
|
| 213 |
+
print(f"F(x,y) = ∇φ = [{Vx_form1}, {Vy_form1}]")
|
| 214 |
+
matches_form1 = Vx_form1.equals(self.a1*self.x + self.b1*self.y + self.c1) and Vy_form1.equals(self.b1*self.x + self.b2*self.y + self.c2)
|
| 215 |
+
print(f"Matches Form 1 from case study: {matches_form1}")
|
| 216 |
+
print()
|
| 217 |
+
|
| 218 |
+
# Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)
|
| 219 |
+
print("Form 2 (Equation 3.44):")
|
| 220 |
+
phi_form2 = self.a2 * self.x**2 / 2 + self.b1 * self.y**2 / 2 + self.c1 * self.y + self.x * (self.a1 * self.y + self.c2)
|
| 221 |
+
print(f"φ(x,y) = {phi_form2}")
|
| 222 |
+
|
| 223 |
+
Vx_form2 = sp.diff(phi_form2, self.x)
|
| 224 |
+
Vy_form2 = sp.diff(phi_form2, self.y)
|
| 225 |
+
print(f"F(x,y) = ∇φ = [{Vx_form2}, {Vy_form2}]")
|
| 226 |
+
matches_form2 = Vx_form2.equals(self.a2*self.x + self.a1*self.y + self.c2) and Vy_form2.equals(self.a1*self.x + self.b1*self.y + self.c1)
|
| 227 |
+
print(f"Matches Form 2 from case study: {matches_form2}")
|
| 228 |
+
print()
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
class GradientFieldFactory:
|
| 232 |
+
"""Factory class to create different types of gradient fields"""
|
| 233 |
+
|
| 234 |
+
@staticmethod
|
| 235 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 236 |
+
return ConstantComponentField(component, constant)
|
| 237 |
+
|
| 238 |
+
@staticmethod
|
| 239 |
+
def create_linear_component_field():
|
| 240 |
+
return LinearComponentField()
|
| 241 |
+
|
| 242 |
+
@staticmethod
|
| 243 |
+
def analyze_all_cases():
|
| 244 |
+
"""Analyze all cases from the case study"""
|
| 245 |
+
print("=" * 60)
|
| 246 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 247 |
+
print("=" * 60)
|
| 248 |
+
|
| 249 |
+
# Case 1: Constant component
|
| 250 |
+
print("\nCASE 1: One Component is Constant")
|
| 251 |
+
print("-" * 40)
|
| 252 |
+
|
| 253 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 254 |
+
phi1 = case1_x.find_potential()
|
| 255 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 256 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 257 |
+
|
| 258 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 259 |
+
phi2 = case1_y.find_potential()
|
| 260 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 261 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 262 |
+
|
| 263 |
+
# Show specific examples
|
| 264 |
+
print("\nSpecific Examples for Case 1:")
|
| 265 |
+
examples1 = case1_x.get_specific_cases()
|
| 266 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 267 |
+
print(f"{case_name}:")
|
| 268 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 269 |
+
|
| 270 |
+
# Case 2: Linear components
|
| 271 |
+
print("\nCASE 2: Both Components are Linear")
|
| 272 |
+
print("-" * 40)
|
| 273 |
+
|
| 274 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 275 |
+
|
| 276 |
+
# Show gradient condition
|
| 277 |
+
condition = case2.get_gradient_condition()
|
| 278 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 279 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 280 |
+
|
| 281 |
+
# Show specific gradient cases
|
| 282 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 283 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 284 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 285 |
+
print(f"{case_name}:")
|
| 286 |
+
case2.display_results(phi, Vx, Vy)
|
| 287 |
+
|
| 288 |
+
# Demonstrate the exact forms from the case study
|
| 289 |
+
case2.demonstrate_case_study_forms()
|
| 290 |
+
|
| 291 |
+
return case1_x, case1_y, case2
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
class NumericalExamples:
|
| 295 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 296 |
+
|
| 297 |
+
def __init__(self):
|
| 298 |
+
self.x, self.y = sp.symbols('x y')
|
| 299 |
+
self.constant_analyzer = ConstantComponentField()
|
| 300 |
+
self.linear_analyzer = LinearComponentField()
|
| 301 |
+
|
| 302 |
+
def run_case1_examples(self):
|
| 303 |
+
"""Run numerical examples for Case 1"""
|
| 304 |
+
print("\nNumerical Examples - Case 1:")
|
| 305 |
+
print("-" * 40)
|
| 306 |
+
|
| 307 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 308 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 309 |
+
Vx1 = 2
|
| 310 |
+
Vy1 = 3 * self.y**2
|
| 311 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 312 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 313 |
+
|
| 314 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 315 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 316 |
+
Vx2 = sp.sin(self.x)
|
| 317 |
+
Vy2 = 5
|
| 318 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 319 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 320 |
+
|
| 321 |
+
def run_case2_examples(self):
|
| 322 |
+
"""Run numerical examples for Case 2"""
|
| 323 |
+
print("\nNumerical Examples - Case 2:")
|
| 324 |
+
print("-" * 40)
|
| 325 |
+
|
| 326 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 327 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 328 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 329 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 330 |
+
|
| 331 |
+
# Check if it's a gradient field first
|
| 332 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 333 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 334 |
+
|
| 335 |
+
if is_gradient:
|
| 336 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 337 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 338 |
+
|
| 339 |
+
# Show that this matches Form 1 from the case study
|
| 340 |
+
print("This matches Form 1 from case study with:")
|
| 341 |
+
print(" a1 = 2, b1 = 3, c1 = 1")
|
| 342 |
+
print(" a2 = 3, b2 = 4, c2 = 2")
|
| 343 |
+
phi_form1 = 2*self.x**2/2 + 4*self.y**2/2 + 2*self.y + self.x*(3*self.y + 1)
|
| 344 |
+
print(f" φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 345 |
+
print(f" = {sp.simplify(phi_form1)}")
|
| 346 |
+
else:
|
| 347 |
+
print("This field cannot have a potential function!")
|
| 348 |
+
print("-" * 50)
|
| 349 |
+
|
| 350 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 351 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 352 |
+
Vx2 = self.x + 2*self.y
|
| 353 |
+
Vy2 = 2*self.x + 3*self.y
|
| 354 |
+
|
| 355 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 356 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 357 |
+
|
| 358 |
+
if is_gradient2:
|
| 359 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 360 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 361 |
+
else:
|
| 362 |
+
print("This field cannot have a potential function!")
|
| 363 |
+
print("-" * 50)
|
| 364 |
+
|
| 365 |
+
# Example 3: Non-gradient field (should fail)
|
| 366 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 367 |
+
Vx3 = self.x + self.y
|
| 368 |
+
Vy3 = 2*self.x + self.y
|
| 369 |
+
|
| 370 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 371 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 372 |
+
|
| 373 |
+
if is_gradient3:
|
| 374 |
+
print("ERROR: This should not be a gradient field!")
|
| 375 |
+
print("Let's check the gradient condition:")
|
| 376 |
+
print(f" ∂P/∂y = {sp.diff(Vx3, self.y)}")
|
| 377 |
+
print(f" ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 378 |
+
print(f" Gradient condition satisfied? {sp.diff(Vx3, self.y).equals(sp.diff(Vy3, self.x))}")
|
| 379 |
+
else:
|
| 380 |
+
print("✓ Correct: This field cannot have a potential function!")
|
| 381 |
+
print(f"Reason: ∂P/∂y = {sp.diff(Vx3, self.y)} ≠ ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 382 |
+
print("The gradient condition a₂ = b₁ is not satisfied")
|
| 383 |
+
|
| 384 |
+
# Test our robust integration method
|
| 385 |
+
print("\nTesting robust integration method:")
|
| 386 |
+
try:
|
| 387 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 388 |
+
print(f"Unexpected success: φ(x,y) = {phi3}")
|
| 389 |
+
except ValueError as e:
|
| 390 |
+
print(f"✓ Robust method correctly failed: {e}")
|
| 391 |
+
print("-" * 50)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def main():
|
| 395 |
+
# Run the complete analysis
|
| 396 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 397 |
+
|
| 398 |
+
# Run numerical examples
|
| 399 |
+
print("\n" + "=" * 60)
|
| 400 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 401 |
+
print("=" * 60)
|
| 402 |
+
|
| 403 |
+
numerical_examples = NumericalExamples()
|
| 404 |
+
numerical_examples.run_case1_examples()
|
| 405 |
+
numerical_examples.run_case2_examples()
|
| 406 |
+
|
| 407 |
+
# Additional demonstration of the key insight
|
| 408 |
+
print("\n" + "=" * 60)
|
| 409 |
+
print("KEY INSIGHT FROM CASE STUDY")
|
| 410 |
+
print("=" * 60)
|
| 411 |
+
print("For linear vector fields F(x,y) = [a₁x + b₁y + c₁, a₂x + b₂y + c₂]:")
|
| 412 |
+
print("• The field is a gradient field IF AND ONLY IF a₂ = b₁")
|
| 413 |
+
print("• The potential function has two equivalent forms:")
|
| 414 |
+
print(" Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 415 |
+
print(" Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 416 |
+
print("• These match equations (3.44) from the case study")
|
| 417 |
+
print("\nSUMMARY OF RESULTS:")
|
| 418 |
+
print("• Case 1 (constant component): Always a gradient field")
|
| 419 |
+
print("• Case 2 (linear components): Gradient field only when a₂ = b₁")
|
| 420 |
+
print("• The robust integration method correctly identifies non-gradient fields")
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
if __name__ == "__main__":
|
| 424 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_final.py
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
is_valid = diff_x.equals(0) and diff_y.equals(0)
|
| 29 |
+
return is_valid, diff_x, diff_y
|
| 30 |
+
|
| 31 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 32 |
+
"""Display the results in a formatted way"""
|
| 33 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 34 |
+
print(f"Potential Function: φ(x,y) = {sp.simplify(phi)}")
|
| 35 |
+
|
| 36 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 37 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 38 |
+
if not is_valid:
|
| 39 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 40 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 41 |
+
print("-" * 50)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 45 |
+
"""Case 1: One component is constant"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 48 |
+
super().__init__()
|
| 49 |
+
self.constant_component = constant_component.lower()
|
| 50 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 51 |
+
|
| 52 |
+
# Define unknown functions
|
| 53 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 54 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 55 |
+
|
| 56 |
+
def find_potential(self) -> sp.Expr:
|
| 57 |
+
if self.constant_component == 'x':
|
| 58 |
+
# F(x,y) = [c, F_y(y)]
|
| 59 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 60 |
+
else: # constant y-component
|
| 61 |
+
# F(x,y) = [F_x(x), c]
|
| 62 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 63 |
+
|
| 64 |
+
return phi
|
| 65 |
+
|
| 66 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 67 |
+
if self.constant_component == 'x':
|
| 68 |
+
return self.c, self.F_y
|
| 69 |
+
else:
|
| 70 |
+
return self.F_x, self.c
|
| 71 |
+
|
| 72 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 73 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 74 |
+
# Check which component is constant
|
| 75 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 76 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 77 |
+
|
| 78 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 79 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 80 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 81 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 82 |
+
|
| 83 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 84 |
+
# F(x,y) = [constant, Vy]
|
| 85 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 86 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 87 |
+
# F(x,y) = [Vx, constant]
|
| 88 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 89 |
+
else:
|
| 90 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 91 |
+
return sp.simplify(phi)
|
| 92 |
+
|
| 93 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 94 |
+
"""Return specific cases with actual functions"""
|
| 95 |
+
cases = {}
|
| 96 |
+
|
| 97 |
+
# Case 1a: Constant x-component with specific F_y
|
| 98 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 99 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 100 |
+
Vy1 = F_y_specific
|
| 101 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 102 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 103 |
+
|
| 104 |
+
# Case 1b: Constant y-component with specific F_x
|
| 105 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 106 |
+
Vx2 = F_x_specific
|
| 107 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 108 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 109 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 110 |
+
|
| 111 |
+
return cases
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 115 |
+
"""Case 2: Both components are linear functions"""
|
| 116 |
+
|
| 117 |
+
def __init__(self):
|
| 118 |
+
super().__init__()
|
| 119 |
+
# Define constants for linear functions
|
| 120 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 121 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 122 |
+
|
| 123 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 124 |
+
"""Find potential function for given linear vector field"""
|
| 125 |
+
if Vx is None or Vy is None:
|
| 126 |
+
Vx, Vy = self.get_general_linear_field()
|
| 127 |
+
|
| 128 |
+
# Use the robust method
|
| 129 |
+
phi = self._find_potential_robust(Vx, Vy)
|
| 130 |
+
return phi
|
| 131 |
+
|
| 132 |
+
def _find_potential_robust(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 133 |
+
"""Robust implementation that properly checks gradient condition"""
|
| 134 |
+
# First check if it's a gradient field
|
| 135 |
+
if not self.check_gradient_condition_for_specific(Vx, Vy):
|
| 136 |
+
raise ValueError("Field is not a gradient field - cannot find potential")
|
| 137 |
+
|
| 138 |
+
# Method: Integrate Vx with respect to x
|
| 139 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 140 |
+
|
| 141 |
+
# Differentiate with respect to y and compare with Vy
|
| 142 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 143 |
+
remaining = Vy - diff_phi_x_y
|
| 144 |
+
|
| 145 |
+
# CRITICAL: Check if remaining depends only on y
|
| 146 |
+
remaining_free_symbols = remaining.free_symbols if hasattr(remaining, 'free_symbols') else set()
|
| 147 |
+
if self.x in remaining_free_symbols:
|
| 148 |
+
raise ValueError(f"Field is not a gradient field - remaining term depends on x: {remaining}")
|
| 149 |
+
|
| 150 |
+
# Integrate remaining with respect to y
|
| 151 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 152 |
+
|
| 153 |
+
# Combine results
|
| 154 |
+
phi = phi_x + phi_y
|
| 155 |
+
|
| 156 |
+
return sp.simplify(phi)
|
| 157 |
+
|
| 158 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 159 |
+
return self.get_general_linear_field()
|
| 160 |
+
|
| 161 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 162 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 163 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 164 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 165 |
+
return Vx, Vy
|
| 166 |
+
|
| 167 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 168 |
+
"""Return the condition for the field to be a gradient field"""
|
| 169 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 170 |
+
Vx, Vy = self.get_general_linear_field()
|
| 171 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 172 |
+
return sp.simplify(condition)
|
| 173 |
+
|
| 174 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 175 |
+
"""Find potential for a specific numeric vector field using robust integration"""
|
| 176 |
+
return self._find_potential_robust(Vx, Vy)
|
| 177 |
+
|
| 178 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 179 |
+
"""Return specific cases where the field is a gradient field"""
|
| 180 |
+
cases = {}
|
| 181 |
+
|
| 182 |
+
# Case 2a: a2 = b1 (from the case study) - FORM 1
|
| 183 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 184 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 185 |
+
phi1 = self._find_potential_robust(Vx1, Vy1)
|
| 186 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 187 |
+
|
| 188 |
+
# Case 2b: b2 = a1 (alternative symmetric case) - FORM 2
|
| 189 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 190 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 191 |
+
phi2 = self._find_potential_robust(Vx2, Vy2)
|
| 192 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 193 |
+
|
| 194 |
+
return cases
|
| 195 |
+
|
| 196 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 197 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 198 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 199 |
+
return sp.simplify(condition).equals(0)
|
| 200 |
+
|
| 201 |
+
def demonstrate_case_study_forms(self):
|
| 202 |
+
"""Demonstrate the exact forms from the case study"""
|
| 203 |
+
print("\nEXACT FORMS FROM CASE STUDY:")
|
| 204 |
+
print("=" * 50)
|
| 205 |
+
|
| 206 |
+
# Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)
|
| 207 |
+
print("Form 1 (Equation 3.44):")
|
| 208 |
+
phi_form1 = self.a1 * self.x**2 / 2 + self.b2 * self.y**2 / 2 + self.c2 * self.y + self.x * (self.b1 * self.y + self.c1)
|
| 209 |
+
print(f"φ(x,y) = {phi_form1}")
|
| 210 |
+
|
| 211 |
+
Vx_form1 = sp.diff(phi_form1, self.x)
|
| 212 |
+
Vy_form1 = sp.diff(phi_form1, self.y)
|
| 213 |
+
print(f"F(x,y) = ∇φ = [{Vx_form1}, {Vy_form1}]")
|
| 214 |
+
matches_form1 = Vx_form1.equals(self.a1*self.x + self.b1*self.y + self.c1) and Vy_form1.equals(self.b1*self.x + self.b2*self.y + self.c2)
|
| 215 |
+
print(f"Matches Form 1 from case study: {matches_form1}")
|
| 216 |
+
print()
|
| 217 |
+
|
| 218 |
+
# Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)
|
| 219 |
+
print("Form 2 (Equation 3.44):")
|
| 220 |
+
phi_form2 = self.a2 * self.x**2 / 2 + self.b1 * self.y**2 / 2 + self.c1 * self.y + self.x * (self.a1 * self.y + self.c2)
|
| 221 |
+
print(f"φ(x,y) = {phi_form2}")
|
| 222 |
+
|
| 223 |
+
Vx_form2 = sp.diff(phi_form2, self.x)
|
| 224 |
+
Vy_form2 = sp.diff(phi_form2, self.y)
|
| 225 |
+
print(f"F(x,y) = ∇φ = [{Vx_form2}, {Vy_form2}]")
|
| 226 |
+
matches_form2 = Vx_form2.equals(self.a2*self.x + self.a1*self.y + self.c2) and Vy_form2.equals(self.a1*self.x + self.b1*self.y + self.c1)
|
| 227 |
+
print(f"Matches Form 2 from case study: {matches_form2}")
|
| 228 |
+
print()
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
class GradientFieldFactory:
|
| 232 |
+
"""Factory class to create different types of gradient fields"""
|
| 233 |
+
|
| 234 |
+
@staticmethod
|
| 235 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 236 |
+
return ConstantComponentField(component, constant)
|
| 237 |
+
|
| 238 |
+
@staticmethod
|
| 239 |
+
def create_linear_component_field():
|
| 240 |
+
return LinearComponentField()
|
| 241 |
+
|
| 242 |
+
@staticmethod
|
| 243 |
+
def analyze_all_cases():
|
| 244 |
+
"""Analyze all cases from the case study"""
|
| 245 |
+
print("=" * 60)
|
| 246 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 247 |
+
print("=" * 60)
|
| 248 |
+
|
| 249 |
+
# Case 1: Constant component
|
| 250 |
+
print("\nCASE 1: One Component is Constant")
|
| 251 |
+
print("-" * 40)
|
| 252 |
+
|
| 253 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 254 |
+
phi1 = case1_x.find_potential()
|
| 255 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 256 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 257 |
+
|
| 258 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 259 |
+
phi2 = case1_y.find_potential()
|
| 260 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 261 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 262 |
+
|
| 263 |
+
# Show specific examples
|
| 264 |
+
print("\nSpecific Examples for Case 1:")
|
| 265 |
+
examples1 = case1_x.get_specific_cases()
|
| 266 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 267 |
+
print(f"{case_name}:")
|
| 268 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 269 |
+
|
| 270 |
+
# Case 2: Linear components
|
| 271 |
+
print("\nCASE 2: Both Components are Linear")
|
| 272 |
+
print("-" * 40)
|
| 273 |
+
|
| 274 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 275 |
+
|
| 276 |
+
# Show gradient condition
|
| 277 |
+
condition = case2.get_gradient_condition()
|
| 278 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 279 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 280 |
+
|
| 281 |
+
# Show specific gradient cases
|
| 282 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 283 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 284 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 285 |
+
print(f"{case_name}:")
|
| 286 |
+
case2.display_results(phi, Vx, Vy)
|
| 287 |
+
|
| 288 |
+
# Demonstrate the exact forms from the case study
|
| 289 |
+
case2.demonstrate_case_study_forms()
|
| 290 |
+
|
| 291 |
+
return case1_x, case1_y, case2
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
class NumericalExamples:
|
| 295 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 296 |
+
|
| 297 |
+
def __init__(self):
|
| 298 |
+
self.x, self.y = sp.symbols('x y')
|
| 299 |
+
self.constant_analyzer = ConstantComponentField()
|
| 300 |
+
self.linear_analyzer = LinearComponentField()
|
| 301 |
+
|
| 302 |
+
def run_case1_examples(self):
|
| 303 |
+
"""Run numerical examples for Case 1"""
|
| 304 |
+
print("\nNumerical Examples - Case 1:")
|
| 305 |
+
print("-" * 40)
|
| 306 |
+
|
| 307 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 308 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 309 |
+
Vx1 = 2
|
| 310 |
+
Vy1 = 3 * self.y**2
|
| 311 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 312 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 313 |
+
|
| 314 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 315 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 316 |
+
Vx2 = sp.sin(self.x)
|
| 317 |
+
Vy2 = 5
|
| 318 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 319 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 320 |
+
|
| 321 |
+
def run_case2_examples(self):
|
| 322 |
+
"""Run numerical examples for Case 2"""
|
| 323 |
+
print("\nNumerical Examples - Case 2:")
|
| 324 |
+
print("-" * 40)
|
| 325 |
+
|
| 326 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 327 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 328 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 329 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 330 |
+
|
| 331 |
+
# Check if it's a gradient field first
|
| 332 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 333 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 334 |
+
|
| 335 |
+
if is_gradient:
|
| 336 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 337 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 338 |
+
|
| 339 |
+
# Show that this matches Form 1 from the case study
|
| 340 |
+
print("This matches Form 1 from case study with:")
|
| 341 |
+
print(" a1 = 2, b1 = 3, c1 = 1")
|
| 342 |
+
print(" a2 = 3, b2 = 4, c2 = 2")
|
| 343 |
+
phi_form1 = 2*self.x**2/2 + 4*self.y**2/2 + 2*self.y + self.x*(3*self.y + 1)
|
| 344 |
+
print(f" φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 345 |
+
print(f" = {sp.simplify(phi_form1)}")
|
| 346 |
+
else:
|
| 347 |
+
print("This field cannot have a potential function!")
|
| 348 |
+
print("-" * 50)
|
| 349 |
+
|
| 350 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 351 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 352 |
+
Vx2 = self.x + 2*self.y
|
| 353 |
+
Vy2 = 2*self.x + 3*self.y
|
| 354 |
+
|
| 355 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 356 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 357 |
+
|
| 358 |
+
if is_gradient2:
|
| 359 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 360 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 361 |
+
else:
|
| 362 |
+
print("This field cannot have a potential function!")
|
| 363 |
+
print("-" * 50)
|
| 364 |
+
|
| 365 |
+
# Example 3: Non-gradient field (should fail)
|
| 366 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 367 |
+
Vx3 = self.x + self.y
|
| 368 |
+
Vy3 = 2*self.x + self.y
|
| 369 |
+
|
| 370 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 371 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 372 |
+
|
| 373 |
+
if is_gradient3:
|
| 374 |
+
print("ERROR: This should not be a gradient field!")
|
| 375 |
+
print("Let's check the gradient condition:")
|
| 376 |
+
print(f" ∂P/∂y = {sp.diff(Vx3, self.y)}")
|
| 377 |
+
print(f" ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 378 |
+
print(f" Gradient condition satisfied? {sp.diff(Vx3, self.y).equals(sp.diff(Vy3, self.x))}")
|
| 379 |
+
else:
|
| 380 |
+
print("✓ Correct: This field cannot have a potential function!")
|
| 381 |
+
print(f"Reason: ∂P/∂y = {sp.diff(Vx3, self.y)} ≠ ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 382 |
+
print("The gradient condition a₂ = b₁ is not satisfied")
|
| 383 |
+
|
| 384 |
+
# Test our robust integration method
|
| 385 |
+
print("\nTesting robust integration method:")
|
| 386 |
+
try:
|
| 387 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 388 |
+
print(f"Unexpected success: φ(x,y) = {phi3}")
|
| 389 |
+
except ValueError as e:
|
| 390 |
+
print(f"✓ Robust method correctly failed: {e}")
|
| 391 |
+
print("-" * 50)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def main():
|
| 395 |
+
# Run the complete analysis
|
| 396 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 397 |
+
|
| 398 |
+
# Run numerical examples
|
| 399 |
+
print("\n" + "=" * 60)
|
| 400 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 401 |
+
print("=" * 60)
|
| 402 |
+
|
| 403 |
+
numerical_examples = NumericalExamples()
|
| 404 |
+
numerical_examples.run_case1_examples()
|
| 405 |
+
numerical_examples.run_case2_examples()
|
| 406 |
+
|
| 407 |
+
# Final summary
|
| 408 |
+
print("\n" + "=" * 60)
|
| 409 |
+
print("FINAL SUMMARY: CONSTRUCTING GRADIENT FIELDS")
|
| 410 |
+
print("=" * 60)
|
| 411 |
+
|
| 412 |
+
print("\n✓ CASE 1: One Component Constant")
|
| 413 |
+
print(" • Always a gradient field")
|
| 414 |
+
print(" • Potential: φ(x,y) = c·x + ∫Fᵧ(y)dy OR φ(x,y) = c·y + ∫Fₓ(x)dx")
|
| 415 |
+
|
| 416 |
+
print("\n✓ CASE 2: Both Components Linear")
|
| 417 |
+
print(" • Gradient field IF AND ONLY IF a₂ = b₁")
|
| 418 |
+
print(" • Potential forms:")
|
| 419 |
+
print(" Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 420 |
+
print(" Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 421 |
+
|
| 422 |
+
print("\n✓ KEY MATHEMATICAL INSIGHTS:")
|
| 423 |
+
print(" • Gradient condition: ∂P/∂y = ∂Q/∂x")
|
| 424 |
+
print(" • For linear fields: a₂ = b₁")
|
| 425 |
+
print(" • Robust integration method prevents false positives")
|
| 426 |
+
print(" • Matches case study equations (3.44) and (3.45)")
|
| 427 |
+
|
| 428 |
+
print("\n✓ OBJECT-ORIENTED DESIGN:")
|
| 429 |
+
print(" • Abstract base class for extensibility")
|
| 430 |
+
print(" • Factory pattern for creating analyzers")
|
| 431 |
+
print(" • Robust verification and error handling")
|
| 432 |
+
print(" • Clear separation of symbolic and numerical analysis")
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
if __name__ == "__main__":
|
| 436 |
+
main()
|
Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_optimized_final.py
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
is_valid = diff_x.equals(0) and diff_y.equals(0)
|
| 29 |
+
return is_valid, diff_x, diff_y
|
| 30 |
+
|
| 31 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 32 |
+
"""Display the results in a formatted way"""
|
| 33 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 34 |
+
print(f"Potential Function: φ(x,y) = {sp.simplify(phi)}")
|
| 35 |
+
|
| 36 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 37 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 38 |
+
if not is_valid:
|
| 39 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 40 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 41 |
+
print("-" * 50)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 45 |
+
"""Case 1: One component is constant"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 48 |
+
super().__init__()
|
| 49 |
+
self.constant_component = constant_component.lower()
|
| 50 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 51 |
+
|
| 52 |
+
# Define unknown functions
|
| 53 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 54 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 55 |
+
|
| 56 |
+
def find_potential(self) -> sp.Expr:
|
| 57 |
+
if self.constant_component == 'x':
|
| 58 |
+
# F(x,y) = [c, F_y(y)]
|
| 59 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 60 |
+
else: # constant y-component
|
| 61 |
+
# F(x,y) = [F_x(x), c]
|
| 62 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 63 |
+
|
| 64 |
+
return phi
|
| 65 |
+
|
| 66 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 67 |
+
if self.constant_component == 'x':
|
| 68 |
+
return self.c, self.F_y
|
| 69 |
+
else:
|
| 70 |
+
return self.F_x, self.c
|
| 71 |
+
|
| 72 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 73 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 74 |
+
# Check which component is constant
|
| 75 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 76 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 77 |
+
|
| 78 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 79 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 80 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 81 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 82 |
+
|
| 83 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 84 |
+
# F(x,y) = [constant, Vy]
|
| 85 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 86 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 87 |
+
# F(x,y) = [Vx, constant]
|
| 88 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 89 |
+
else:
|
| 90 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 91 |
+
return sp.simplify(phi)
|
| 92 |
+
|
| 93 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 94 |
+
"""Return specific cases with actual functions"""
|
| 95 |
+
cases = {}
|
| 96 |
+
|
| 97 |
+
# Case 1a: Constant x-component with specific F_y
|
| 98 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 99 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 100 |
+
Vy1 = F_y_specific
|
| 101 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 102 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 103 |
+
|
| 104 |
+
# Case 1b: Constant y-component with specific F_x
|
| 105 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 106 |
+
Vx2 = F_x_specific
|
| 107 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 108 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 109 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 110 |
+
|
| 111 |
+
return cases
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 115 |
+
"""Case 2: Both components are linear functions"""
|
| 116 |
+
|
| 117 |
+
def __init__(self):
|
| 118 |
+
super().__init__()
|
| 119 |
+
# Define constants for linear functions
|
| 120 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 121 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 122 |
+
|
| 123 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 124 |
+
"""Find potential function for given linear vector field"""
|
| 125 |
+
if Vx is None or Vy is None:
|
| 126 |
+
Vx, Vy = self.get_general_linear_field()
|
| 127 |
+
|
| 128 |
+
# Use the robust method
|
| 129 |
+
phi = self._find_potential_robust(Vx, Vy)
|
| 130 |
+
return phi
|
| 131 |
+
|
| 132 |
+
def _find_potential_robust(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 133 |
+
"""Robust implementation that properly checks gradient condition"""
|
| 134 |
+
# First check if it's a gradient field
|
| 135 |
+
if not self.check_gradient_condition_for_specific(Vx, Vy):
|
| 136 |
+
raise ValueError("Field is not a gradient field - cannot find potential")
|
| 137 |
+
|
| 138 |
+
# Method: Integrate Vx with respect to x
|
| 139 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 140 |
+
|
| 141 |
+
# Differentiate with respect to y and compare with Vy
|
| 142 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 143 |
+
remaining = Vy - diff_phi_x_y
|
| 144 |
+
|
| 145 |
+
# CRITICAL: Check if remaining depends only on y
|
| 146 |
+
remaining_free_symbols = remaining.free_symbols if hasattr(remaining, 'free_symbols') else set()
|
| 147 |
+
if self.x in remaining_free_symbols:
|
| 148 |
+
raise ValueError(f"Field is not a gradient field - remaining term depends on x: {remaining}")
|
| 149 |
+
|
| 150 |
+
# Integrate remaining with respect to y
|
| 151 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 152 |
+
|
| 153 |
+
# Combine results
|
| 154 |
+
phi = phi_x + phi_y
|
| 155 |
+
|
| 156 |
+
return sp.simplify(phi)
|
| 157 |
+
|
| 158 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 159 |
+
return self.get_general_linear_field()
|
| 160 |
+
|
| 161 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 162 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 163 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 164 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 165 |
+
return Vx, Vy
|
| 166 |
+
|
| 167 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 168 |
+
"""Return the condition for the field to be a gradient field"""
|
| 169 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 170 |
+
Vx, Vy = self.get_general_linear_field()
|
| 171 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 172 |
+
return sp.simplify(condition)
|
| 173 |
+
|
| 174 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 175 |
+
"""Find potential for a specific numeric vector field using robust integration"""
|
| 176 |
+
return self._find_potential_robust(Vx, Vy)
|
| 177 |
+
|
| 178 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 179 |
+
"""Return specific cases where the field is a gradient field"""
|
| 180 |
+
cases = {}
|
| 181 |
+
|
| 182 |
+
# Case 2a: a2 = b1 (from the case study) - FORM 1
|
| 183 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 184 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 185 |
+
phi1 = self._find_potential_robust(Vx1, Vy1)
|
| 186 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 187 |
+
|
| 188 |
+
# Case 2b: b2 = a1 (alternative symmetric case) - FORM 2
|
| 189 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 190 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 191 |
+
phi2 = self._find_potential_robust(Vx2, Vy2)
|
| 192 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 193 |
+
|
| 194 |
+
return cases
|
| 195 |
+
|
| 196 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 197 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 198 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 199 |
+
return sp.simplify(condition).equals(0)
|
| 200 |
+
|
| 201 |
+
def demonstrate_case_study_forms(self):
|
| 202 |
+
"""Demonstrate the exact forms from the case study"""
|
| 203 |
+
print("\nEXACT FORMS FROM CASE STUDY:")
|
| 204 |
+
print("=" * 50)
|
| 205 |
+
|
| 206 |
+
# Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)
|
| 207 |
+
print("Form 1 (Equation 3.44):")
|
| 208 |
+
phi_form1 = self.a1 * self.x**2 / 2 + self.b2 * self.y**2 / 2 + self.c2 * self.y + self.x * (self.b1 * self.y + self.c1)
|
| 209 |
+
print(f"φ(x,y) = {phi_form1}")
|
| 210 |
+
|
| 211 |
+
Vx_form1 = sp.diff(phi_form1, self.x)
|
| 212 |
+
Vy_form1 = sp.diff(phi_form1, self.y)
|
| 213 |
+
print(f"F(x,y) = ∇φ = [{Vx_form1}, {Vy_form1}]")
|
| 214 |
+
matches_form1 = Vx_form1.equals(self.a1*self.x + self.b1*self.y + self.c1) and Vy_form1.equals(self.b1*self.x + self.b2*self.y + self.c2)
|
| 215 |
+
print(f"Matches Form 1 from case study: {matches_form1}")
|
| 216 |
+
print()
|
| 217 |
+
|
| 218 |
+
# Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)
|
| 219 |
+
print("Form 2 (Equation 3.44):")
|
| 220 |
+
phi_form2 = self.a2 * self.x**2 / 2 + self.b1 * self.y**2 / 2 + self.c1 * self.y + self.x * (self.a1 * self.y + self.c2)
|
| 221 |
+
print(f"φ(x,y) = {phi_form2}")
|
| 222 |
+
|
| 223 |
+
Vx_form2 = sp.diff(phi_form2, self.x)
|
| 224 |
+
Vy_form2 = sp.diff(phi_form2, self.y)
|
| 225 |
+
print(f"F(x,y) = ∇φ = [{Vx_form2}, {Vy_form2}]")
|
| 226 |
+
matches_form2 = Vx_form2.equals(self.a2*self.x + self.a1*self.y + self.c2) and Vy_form2.equals(self.a1*self.x + self.b1*self.y + self.c1)
|
| 227 |
+
print(f"Matches Form 2 from case study: {matches_form2}")
|
| 228 |
+
print()
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
class GradientFieldFactory:
|
| 232 |
+
"""Factory class to create different types of gradient fields"""
|
| 233 |
+
|
| 234 |
+
@staticmethod
|
| 235 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 236 |
+
return ConstantComponentField(component, constant)
|
| 237 |
+
|
| 238 |
+
@staticmethod
|
| 239 |
+
def create_linear_component_field():
|
| 240 |
+
return LinearComponentField()
|
| 241 |
+
|
| 242 |
+
@staticmethod
|
| 243 |
+
def analyze_all_cases():
|
| 244 |
+
"""Analyze all cases from the case study"""
|
| 245 |
+
print("=" * 60)
|
| 246 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 247 |
+
print("=" * 60)
|
| 248 |
+
|
| 249 |
+
# Case 1: Constant component
|
| 250 |
+
print("\nCASE 1: One Component is Constant")
|
| 251 |
+
print("-" * 40)
|
| 252 |
+
|
| 253 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 254 |
+
phi1 = case1_x.find_potential()
|
| 255 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 256 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 257 |
+
|
| 258 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 259 |
+
phi2 = case1_y.find_potential()
|
| 260 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 261 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 262 |
+
|
| 263 |
+
# Show specific examples
|
| 264 |
+
print("\nSpecific Examples for Case 1:")
|
| 265 |
+
examples1 = case1_x.get_specific_cases()
|
| 266 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 267 |
+
print(f"{case_name}:")
|
| 268 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 269 |
+
|
| 270 |
+
# Case 2: Linear components
|
| 271 |
+
print("\nCASE 2: Both Components are Linear")
|
| 272 |
+
print("-" * 40)
|
| 273 |
+
|
| 274 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 275 |
+
|
| 276 |
+
# Show gradient condition
|
| 277 |
+
condition = case2.get_gradient_condition()
|
| 278 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 279 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 280 |
+
|
| 281 |
+
# Show specific gradient cases
|
| 282 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 283 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 284 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 285 |
+
print(f"{case_name}:")
|
| 286 |
+
case2.display_results(phi, Vx, Vy)
|
| 287 |
+
|
| 288 |
+
# Demonstrate the exact forms from the case study
|
| 289 |
+
case2.demonstrate_case_study_forms()
|
| 290 |
+
|
| 291 |
+
return case1_x, case1_y, case2
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
class NumericalExamples:
|
| 295 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 296 |
+
|
| 297 |
+
def __init__(self):
|
| 298 |
+
self.x, self.y = sp.symbols('x y')
|
| 299 |
+
self.constant_analyzer = ConstantComponentField()
|
| 300 |
+
self.linear_analyzer = LinearComponentField()
|
| 301 |
+
|
| 302 |
+
def run_case1_examples(self):
|
| 303 |
+
"""Run numerical examples for Case 1"""
|
| 304 |
+
print("\nNumerical Examples - Case 1:")
|
| 305 |
+
print("-" * 40)
|
| 306 |
+
|
| 307 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 308 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 309 |
+
Vx1 = 2
|
| 310 |
+
Vy1 = 3 * self.y**2
|
| 311 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 312 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 313 |
+
|
| 314 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 315 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 316 |
+
Vx2 = sp.sin(self.x)
|
| 317 |
+
Vy2 = 5
|
| 318 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 319 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 320 |
+
|
| 321 |
+
def run_case2_examples(self):
|
| 322 |
+
"""Run numerical examples for Case 2"""
|
| 323 |
+
print("\nNumerical Examples - Case 2:")
|
| 324 |
+
print("-" * 40)
|
| 325 |
+
|
| 326 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 327 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 328 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 329 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 330 |
+
|
| 331 |
+
# Check if it's a gradient field first
|
| 332 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 333 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 334 |
+
|
| 335 |
+
if is_gradient:
|
| 336 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 337 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 338 |
+
|
| 339 |
+
# Show that this matches Form 1 from the case study
|
| 340 |
+
print("This matches Form 1 from case study with:")
|
| 341 |
+
print(" a1 = 2, b1 = 3, c1 = 1")
|
| 342 |
+
print(" a2 = 3, b2 = 4, c2 = 2")
|
| 343 |
+
phi_form1 = 2*self.x**2/2 + 4*self.y**2/2 + 2*self.y + self.x*(3*self.y + 1)
|
| 344 |
+
print(f" φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 345 |
+
print(f" = {sp.simplify(phi_form1)}")
|
| 346 |
+
else:
|
| 347 |
+
print("This field cannot have a potential function!")
|
| 348 |
+
print("-" * 50)
|
| 349 |
+
|
| 350 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 351 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 352 |
+
Vx2 = self.x + 2*self.y
|
| 353 |
+
Vy2 = 2*self.x + 3*self.y
|
| 354 |
+
|
| 355 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 356 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 357 |
+
|
| 358 |
+
if is_gradient2:
|
| 359 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 360 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 361 |
+
else:
|
| 362 |
+
print("This field cannot have a potential function!")
|
| 363 |
+
print("-" * 50)
|
| 364 |
+
|
| 365 |
+
# Example 3: Non-gradient field (should fail)
|
| 366 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 367 |
+
Vx3 = self.x + self.y
|
| 368 |
+
Vy3 = 2*self.x + self.y
|
| 369 |
+
|
| 370 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 371 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 372 |
+
|
| 373 |
+
if is_gradient3:
|
| 374 |
+
print("ERROR: This should not be a gradient field!")
|
| 375 |
+
print("Let's check the gradient condition:")
|
| 376 |
+
print(f" ∂P/∂y = {sp.diff(Vx3, self.y)}")
|
| 377 |
+
print(f" ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 378 |
+
print(f" Gradient condition satisfied? {sp.diff(Vx3, self.y).equals(sp.diff(Vy3, self.x))}")
|
| 379 |
+
else:
|
| 380 |
+
print("✓ Correct: This field cannot have a potential function!")
|
| 381 |
+
print(f"Reason: ∂P/∂y = {sp.diff(Vx3, self.y)} ≠ ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 382 |
+
print("The gradient condition a₂ = b₁ is not satisfied")
|
| 383 |
+
|
| 384 |
+
# Test our robust integration method
|
| 385 |
+
print("\nTesting robust integration method:")
|
| 386 |
+
try:
|
| 387 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 388 |
+
print(f"Unexpected success: φ(x,y) = {phi3}")
|
| 389 |
+
except ValueError as e:
|
| 390 |
+
print(f"✓ Robust method correctly failed: {e}")
|
| 391 |
+
print("-" * 50)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def main():
|
| 395 |
+
# Run the complete analysis
|
| 396 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 397 |
+
|
| 398 |
+
# Run numerical examples
|
| 399 |
+
print("\n" + "=" * 60)
|
| 400 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 401 |
+
print("=" * 60)
|
| 402 |
+
|
| 403 |
+
numerical_examples = NumericalExamples()
|
| 404 |
+
numerical_examples.run_case1_examples()
|
| 405 |
+
numerical_examples.run_case2_examples()
|
| 406 |
+
|
| 407 |
+
# Final summary
|
| 408 |
+
print("\n" + "=" * 60)
|
| 409 |
+
print("FINAL SUMMARY: CONSTRUCTING GRADIENT FIELDS")
|
| 410 |
+
print("=" * 60)
|
| 411 |
+
|
| 412 |
+
print("\n✓ CASE 1: One Component Constant")
|
| 413 |
+
print(" • Always a gradient field")
|
| 414 |
+
print(" • Potential: φ(x,y) = c·x + ∫Fᵧ(y)dy OR φ(x,y) = c·y + ∫Fₓ(x)dx")
|
| 415 |
+
|
| 416 |
+
print("\n✓ CASE 2: Both Components Linear")
|
| 417 |
+
print(" • Gradient field IF AND ONLY IF a₂ = b₁")
|
| 418 |
+
print(" • Potential forms:")
|
| 419 |
+
print(" Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 420 |
+
print(" Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 421 |
+
|
| 422 |
+
print("\n✓ KEY MATHEMATICAL INSIGHTS:")
|
| 423 |
+
print(" • Gradient condition: ∂P/∂y = ∂Q/∂x")
|
| 424 |
+
print(" • For linear fields: a₂ = b₁")
|
| 425 |
+
print(" • Robust integration method prevents false positives")
|
| 426 |
+
print(" • Matches case study equations (3.44) and (3.45)")
|
| 427 |
+
|
| 428 |
+
print("\n✓ OBJECT-ORIENTED DESIGN:")
|
| 429 |
+
print(" • Abstract base class for extensibility")
|
| 430 |
+
print(" • Factory pattern for creating analyzers")
|
| 431 |
+
print(" • Robust verification and error handling")
|
| 432 |
+
print(" • Clear separation of symbolic and numerical analysis")
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
if __name__ == "__main__":
|
| 436 |
+
main()
|
Case Study Constructing Gradient Fields/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
sympy
|
Case Study Constructing Gradient Fields/test_corrected_examples.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
|
| 3 |
+
# Test the corrected numerical examples separately
|
| 4 |
+
def test_corrected_examples():
|
| 5 |
+
x, y = sp.symbols('x y')
|
| 6 |
+
|
| 7 |
+
print("Testing Corrected Numerical Examples:")
|
| 8 |
+
print("=" * 50)
|
| 9 |
+
|
| 10 |
+
# Case 1 Example: F(x,y) = [2, 3y²]
|
| 11 |
+
print("Case 1: F(x,y) = [2, 3y²]")
|
| 12 |
+
Vx = 2
|
| 13 |
+
Vy = 3 * y**2
|
| 14 |
+
|
| 15 |
+
# Manual calculation: φ = ∫2 dx + ∫3y² dy = 2x + y³
|
| 16 |
+
phi_manual = 2*x + y**3
|
| 17 |
+
|
| 18 |
+
# Verify
|
| 19 |
+
print(f"∂φ/∂x = {sp.diff(phi_manual, x)} = Vx? {sp.diff(phi_manual, x) == Vx}")
|
| 20 |
+
print(f"∂φ/∂y = {sp.diff(phi_manual, y)} = Vy? {sp.diff(phi_manual, y) == Vy}")
|
| 21 |
+
print()
|
| 22 |
+
|
| 23 |
+
# Case 2 Example: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]
|
| 24 |
+
print("Case 2: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 25 |
+
Vx2 = 2*x + 3*y + 1
|
| 26 |
+
Vy2 = 3*x + 4*y + 2
|
| 27 |
+
|
| 28 |
+
# Check gradient condition: ∂P/∂y = 3, ∂Q/∂x = 3 → equal, so gradient field exists
|
| 29 |
+
print(f"Gradient condition: ∂P/∂y = {sp.diff(Vx2, y)}, ∂Q/∂x = {sp.diff(Vy2, x)}")
|
| 30 |
+
print(f"Field is gradient? {sp.diff(Vx2, y) == sp.diff(Vy2, x)}")
|
| 31 |
+
|
| 32 |
+
# Find potential using our method
|
| 33 |
+
phi_x = sp.integrate(Vx2, x) # ∫(2x + 3y + 1)dx = x² + 3xy + x
|
| 34 |
+
remaining = Vy2 - sp.diff(phi_x, y) # (3x + 4y + 2) - 3x = 4y + 2
|
| 35 |
+
phi_y = sp.integrate(remaining, y) # ∫(4y + 2)dy = 2y² + 2y
|
| 36 |
+
phi_calculated = phi_x + phi_y # x² + 3xy + x + 2y² + 2y
|
| 37 |
+
|
| 38 |
+
print(f"Calculated potential: φ = {phi_calculated}")
|
| 39 |
+
print(f"∂φ/∂x = {sp.diff(phi_calculated, x)} = Vx? {sp.diff(phi_calculated, x) == Vx2}")
|
| 40 |
+
print(f"∂φ/∂y = {sp.diff(phi_calculated, y)} = Vy? {sp.diff(phi_calculated, y) == Vy2}")
|
| 41 |
+
|
| 42 |
+
# Run the test
|
| 43 |
+
test_corrected_examples()
|
app.py
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import io
|
| 3 |
+
from contextlib import redirect_stdout
|
| 4 |
+
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
| 5 |
+
QHBoxLayout, QTabWidget, QTextEdit, QPushButton,
|
| 6 |
+
QGroupBox, QLabel, QComboBox, QSpinBox, QDoubleSpinBox,
|
| 7 |
+
QLineEdit, QFormLayout, QSplitter, QMessageBox)
|
| 8 |
+
from PyQt5.QtCore import Qt
|
| 9 |
+
from PyQt5.QtGui import QFont, QTextCursor
|
| 10 |
+
|
| 11 |
+
# Import the original script components
|
| 12 |
+
import sympy as sp
|
| 13 |
+
from constructing_gradient_fields_case_1_and_2_optimized_final import (
|
| 14 |
+
GradientFieldFactory, NumericalExamples, ConstantComponentField, LinearComponentField
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
class GradientFieldApp(QMainWindow):
|
| 18 |
+
def __init__(self):
|
| 19 |
+
super().__init__()
|
| 20 |
+
self.initUI()
|
| 21 |
+
|
| 22 |
+
def initUI(self):
|
| 23 |
+
self.setWindowTitle('Gradient Field Analyzer')
|
| 24 |
+
self.setGeometry(100, 100, 1200, 800)
|
| 25 |
+
|
| 26 |
+
# Central widget and main layout
|
| 27 |
+
central_widget = QWidget()
|
| 28 |
+
self.setCentralWidget(central_widget)
|
| 29 |
+
main_layout = QVBoxLayout(central_widget)
|
| 30 |
+
|
| 31 |
+
# Title
|
| 32 |
+
title_label = QLabel('Gradient Field Analyzer')
|
| 33 |
+
title_label.setAlignment(Qt.AlignCenter)
|
| 34 |
+
title_font = QFont()
|
| 35 |
+
title_font.setPointSize(16)
|
| 36 |
+
title_font.setBold(True)
|
| 37 |
+
title_label.setFont(title_font)
|
| 38 |
+
main_layout.addWidget(title_label)
|
| 39 |
+
|
| 40 |
+
# Create tab widget
|
| 41 |
+
self.tabs = QTabWidget()
|
| 42 |
+
main_layout.addWidget(self.tabs)
|
| 43 |
+
|
| 44 |
+
# Create tabs
|
| 45 |
+
self.create_analysis_tab()
|
| 46 |
+
self.create_case1_tab()
|
| 47 |
+
self.create_case2_tab()
|
| 48 |
+
self.create_numerical_examples_tab()
|
| 49 |
+
|
| 50 |
+
# Status bar
|
| 51 |
+
self.statusBar().showMessage('Ready')
|
| 52 |
+
|
| 53 |
+
def create_analysis_tab(self):
|
| 54 |
+
"""Tab for running the complete analysis"""
|
| 55 |
+
tab = QWidget()
|
| 56 |
+
layout = QVBoxLayout(tab)
|
| 57 |
+
|
| 58 |
+
# Description
|
| 59 |
+
desc_label = QLabel(
|
| 60 |
+
"Run the complete gradient field analysis from the case study. "
|
| 61 |
+
"This will analyze both Case 1 (one constant component) and "
|
| 62 |
+
"Case 2 (both linear components)."
|
| 63 |
+
)
|
| 64 |
+
desc_label.setWordWrap(True)
|
| 65 |
+
layout.addWidget(desc_label)
|
| 66 |
+
|
| 67 |
+
# Run analysis button
|
| 68 |
+
run_btn = QPushButton('Run Complete Analysis')
|
| 69 |
+
run_btn.clicked.connect(self.run_complete_analysis)
|
| 70 |
+
layout.addWidget(run_btn)
|
| 71 |
+
|
| 72 |
+
# Output area
|
| 73 |
+
self.analysis_output = QTextEdit()
|
| 74 |
+
self.analysis_output.setReadOnly(True)
|
| 75 |
+
layout.addWidget(self.analysis_output)
|
| 76 |
+
|
| 77 |
+
self.tabs.addTab(tab, "Complete Analysis")
|
| 78 |
+
|
| 79 |
+
def create_case1_tab(self):
|
| 80 |
+
"""Tab for Case 1: One constant component"""
|
| 81 |
+
tab = QWidget()
|
| 82 |
+
layout = QVBoxLayout(tab)
|
| 83 |
+
|
| 84 |
+
# Description
|
| 85 |
+
desc_label = QLabel(
|
| 86 |
+
"Case 1: One component of the vector field is constant. "
|
| 87 |
+
"The potential function can be found by direct integration."
|
| 88 |
+
)
|
| 89 |
+
desc_label.setWordWrap(True)
|
| 90 |
+
layout.addWidget(desc_label)
|
| 91 |
+
|
| 92 |
+
# Input form
|
| 93 |
+
form_group = QGroupBox("Vector Field Parameters")
|
| 94 |
+
form_layout = QFormLayout(form_group)
|
| 95 |
+
|
| 96 |
+
# Component selection
|
| 97 |
+
self.case1_component = QComboBox()
|
| 98 |
+
self.case1_component.addItems(['x', 'y'])
|
| 99 |
+
form_layout.addRow("Constant Component:", self.case1_component)
|
| 100 |
+
|
| 101 |
+
# Constant value
|
| 102 |
+
self.case1_constant = QLineEdit()
|
| 103 |
+
self.case1_constant.setText('2')
|
| 104 |
+
form_layout.addRow("Constant Value:", self.case1_constant)
|
| 105 |
+
|
| 106 |
+
# Function for non-constant component
|
| 107 |
+
self.case1_function = QLineEdit()
|
| 108 |
+
self.case1_function.setText('y**2')
|
| 109 |
+
form_layout.addRow("Function (for non-constant component):", self.case1_function)
|
| 110 |
+
|
| 111 |
+
layout.addWidget(form_group)
|
| 112 |
+
|
| 113 |
+
# Buttons
|
| 114 |
+
btn_layout = QHBoxLayout()
|
| 115 |
+
|
| 116 |
+
analyze_btn = QPushButton('Analyze Case 1')
|
| 117 |
+
analyze_btn.clicked.connect(self.analyze_case1)
|
| 118 |
+
btn_layout.addWidget(analyze_btn)
|
| 119 |
+
|
| 120 |
+
symbolic_btn = QPushButton('Show Symbolic Analysis')
|
| 121 |
+
symbolic_btn.clicked.connect(self.show_symbolic_case1)
|
| 122 |
+
btn_layout.addWidget(symbolic_btn)
|
| 123 |
+
|
| 124 |
+
layout.addLayout(btn_layout)
|
| 125 |
+
|
| 126 |
+
# Output area
|
| 127 |
+
self.case1_output = QTextEdit()
|
| 128 |
+
self.case1_output.setReadOnly(True)
|
| 129 |
+
layout.addWidget(self.case1_output)
|
| 130 |
+
|
| 131 |
+
self.tabs.addTab(tab, "Case 1")
|
| 132 |
+
|
| 133 |
+
def create_case2_tab(self):
|
| 134 |
+
"""Tab for Case 2: Both linear components"""
|
| 135 |
+
tab = QWidget()
|
| 136 |
+
layout = QVBoxLayout(tab)
|
| 137 |
+
|
| 138 |
+
# Description
|
| 139 |
+
desc_label = QLabel(
|
| 140 |
+
"Case 2: Both components of the vector field are linear functions. "
|
| 141 |
+
"The field is a gradient field if and only if a₂ = b₁."
|
| 142 |
+
)
|
| 143 |
+
desc_label.setWordWrap(True)
|
| 144 |
+
layout.addWidget(desc_label)
|
| 145 |
+
|
| 146 |
+
# Input form
|
| 147 |
+
form_group = QGroupBox("Vector Field Parameters")
|
| 148 |
+
form_layout = QFormLayout(form_group)
|
| 149 |
+
|
| 150 |
+
# Coefficients for Vx = a1*x + b1*y + c1
|
| 151 |
+
self.case2_a1 = QLineEdit()
|
| 152 |
+
self.case2_a1.setText('2')
|
| 153 |
+
form_layout.addRow("a₁ (coefficient of x in Vx):", self.case2_a1)
|
| 154 |
+
|
| 155 |
+
self.case2_b1 = QLineEdit()
|
| 156 |
+
self.case2_b1.setText('3')
|
| 157 |
+
form_layout.addRow("b₁ (coefficient of y in Vx):", self.case2_b1)
|
| 158 |
+
|
| 159 |
+
self.case2_c1 = QLineEdit()
|
| 160 |
+
self.case2_c1.setText('1')
|
| 161 |
+
form_layout.addRow("c₁ (constant in Vx):", self.case2_c1)
|
| 162 |
+
|
| 163 |
+
# Coefficients for Vy = a2*x + b2*y + c2
|
| 164 |
+
self.case2_a2 = QLineEdit()
|
| 165 |
+
self.case2_a2.setText('3')
|
| 166 |
+
form_layout.addRow("a₂ (coefficient of x in Vy):", self.case2_a2)
|
| 167 |
+
|
| 168 |
+
self.case2_b2 = QLineEdit()
|
| 169 |
+
self.case2_b2.setText('4')
|
| 170 |
+
form_layout.addRow("b₂ (coefficient of y in Vy):", self.case2_b2)
|
| 171 |
+
|
| 172 |
+
self.case2_c2 = QLineEdit()
|
| 173 |
+
self.case2_c2.setText('2')
|
| 174 |
+
form_layout.addRow("c₂ (constant in Vy):", self.case2_c2)
|
| 175 |
+
|
| 176 |
+
layout.addWidget(form_group)
|
| 177 |
+
|
| 178 |
+
# Buttons
|
| 179 |
+
btn_layout = QHBoxLayout()
|
| 180 |
+
|
| 181 |
+
analyze_btn = QPushButton('Analyze Case 2')
|
| 182 |
+
analyze_btn.clicked.connect(self.analyze_case2)
|
| 183 |
+
btn_layout.addWidget(analyze_btn)
|
| 184 |
+
|
| 185 |
+
symbolic_btn = QPushButton('Show Symbolic Analysis')
|
| 186 |
+
symbolic_btn.clicked.connect(self.show_symbolic_case2)
|
| 187 |
+
btn_layout.addWidget(symbolic_btn)
|
| 188 |
+
|
| 189 |
+
check_btn = QPushButton('Check Gradient Condition')
|
| 190 |
+
check_btn.clicked.connect(self.check_gradient_condition)
|
| 191 |
+
btn_layout.addWidget(check_btn)
|
| 192 |
+
|
| 193 |
+
layout.addLayout(btn_layout)
|
| 194 |
+
|
| 195 |
+
# Output area
|
| 196 |
+
self.case2_output = QTextEdit()
|
| 197 |
+
self.case2_output.setReadOnly(True)
|
| 198 |
+
layout.addWidget(self.case2_output)
|
| 199 |
+
|
| 200 |
+
self.tabs.addTab(tab, "Case 2")
|
| 201 |
+
|
| 202 |
+
def create_numerical_examples_tab(self):
|
| 203 |
+
"""Tab for running numerical examples"""
|
| 204 |
+
tab = QWidget()
|
| 205 |
+
layout = QVBoxLayout(tab)
|
| 206 |
+
|
| 207 |
+
# Description
|
| 208 |
+
desc_label = QLabel(
|
| 209 |
+
"Run numerical examples to verify the gradient field analysis. "
|
| 210 |
+
"These examples demonstrate both valid gradient fields and non-gradient fields."
|
| 211 |
+
)
|
| 212 |
+
desc_label.setWordWrap(True)
|
| 213 |
+
layout.addWidget(desc_label)
|
| 214 |
+
|
| 215 |
+
# Buttons
|
| 216 |
+
btn_layout = QHBoxLayout()
|
| 217 |
+
|
| 218 |
+
case1_examples_btn = QPushButton('Run Case 1 Examples')
|
| 219 |
+
case1_examples_btn.clicked.connect(self.run_case1_examples)
|
| 220 |
+
btn_layout.addWidget(case1_examples_btn)
|
| 221 |
+
|
| 222 |
+
case2_examples_btn = QPushButton('Run Case 2 Examples')
|
| 223 |
+
case2_examples_btn.clicked.connect(self.run_case2_examples)
|
| 224 |
+
btn_layout.addWidget(case2_examples_btn)
|
| 225 |
+
|
| 226 |
+
all_examples_btn = QPushButton('Run All Examples')
|
| 227 |
+
all_examples_btn.clicked.connect(self.run_all_examples)
|
| 228 |
+
btn_layout.addWidget(all_examples_btn)
|
| 229 |
+
|
| 230 |
+
layout.addLayout(btn_layout)
|
| 231 |
+
|
| 232 |
+
# Output area
|
| 233 |
+
self.examples_output = QTextEdit()
|
| 234 |
+
self.examples_output.setReadOnly(True)
|
| 235 |
+
layout.addWidget(self.examples_output)
|
| 236 |
+
|
| 237 |
+
self.tabs.addTab(tab, "Numerical Examples")
|
| 238 |
+
|
| 239 |
+
def run_complete_analysis(self):
|
| 240 |
+
"""Run the complete analysis from the original script"""
|
| 241 |
+
self.analysis_output.clear()
|
| 242 |
+
|
| 243 |
+
# Capture stdout to display in the text area
|
| 244 |
+
output = io.StringIO()
|
| 245 |
+
with redirect_stdout(output):
|
| 246 |
+
try:
|
| 247 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 248 |
+
self.statusBar().showMessage('Complete analysis finished successfully')
|
| 249 |
+
except Exception as e:
|
| 250 |
+
print(f"Error during analysis: {e}")
|
| 251 |
+
self.statusBar().showMessage(f'Error: {e}')
|
| 252 |
+
|
| 253 |
+
self.analysis_output.setPlainText(output.getvalue())
|
| 254 |
+
|
| 255 |
+
def analyze_case1(self):
|
| 256 |
+
"""Analyze a specific Case 1 example"""
|
| 257 |
+
self.case1_output.clear()
|
| 258 |
+
|
| 259 |
+
try:
|
| 260 |
+
# Get parameters from UI
|
| 261 |
+
component = self.case1_component.currentText()
|
| 262 |
+
constant = float(self.case1_constant.text())
|
| 263 |
+
function_str = self.case1_function.text()
|
| 264 |
+
|
| 265 |
+
# Create analyzer
|
| 266 |
+
analyzer = ConstantComponentField(component)
|
| 267 |
+
|
| 268 |
+
# Create vector field
|
| 269 |
+
x, y = sp.symbols('x y')
|
| 270 |
+
if component == 'x':
|
| 271 |
+
Vx = constant
|
| 272 |
+
Vy = sp.sympify(function_str)
|
| 273 |
+
else:
|
| 274 |
+
Vx = sp.sympify(function_str)
|
| 275 |
+
Vy = constant
|
| 276 |
+
|
| 277 |
+
# Find potential
|
| 278 |
+
phi = analyzer.find_potential_for_specific_field(Vx, Vy)
|
| 279 |
+
|
| 280 |
+
# Display results
|
| 281 |
+
output = f"Case 1 Analysis:\n"
|
| 282 |
+
output += f"Vector Field: F(x,y) = [{Vx}, {Vy}]\n"
|
| 283 |
+
output += f"Potential Function: φ(x,y) = {sp.simplify(phi)}\n\n"
|
| 284 |
+
|
| 285 |
+
# Verify
|
| 286 |
+
is_valid, diff_x, diff_y = analyzer.verify_potential(phi, Vx, Vy)
|
| 287 |
+
output += f"Verification: ∇φ = F? {is_valid}\n"
|
| 288 |
+
if not is_valid:
|
| 289 |
+
output += f" ∂φ/∂x - Vx = {diff_x}\n"
|
| 290 |
+
output += f" ∂φ/∂y - Vy = {diff_y}\n"
|
| 291 |
+
|
| 292 |
+
self.case1_output.setPlainText(output)
|
| 293 |
+
self.statusBar().showMessage('Case 1 analysis completed')
|
| 294 |
+
|
| 295 |
+
except Exception as e:
|
| 296 |
+
self.case1_output.setPlainText(f"Error: {e}")
|
| 297 |
+
self.statusBar().showMessage(f'Error in Case 1 analysis: {e}')
|
| 298 |
+
|
| 299 |
+
def show_symbolic_case1(self):
|
| 300 |
+
"""Show symbolic analysis for Case 1"""
|
| 301 |
+
self.case1_output.clear()
|
| 302 |
+
|
| 303 |
+
try:
|
| 304 |
+
component = self.case1_component.currentText()
|
| 305 |
+
analyzer = ConstantComponentField(component)
|
| 306 |
+
|
| 307 |
+
# Get symbolic results
|
| 308 |
+
phi = analyzer.find_potential()
|
| 309 |
+
Vx, Vy = analyzer.get_vector_field()
|
| 310 |
+
|
| 311 |
+
output = f"Symbolic Analysis for Case 1:\n"
|
| 312 |
+
output += f"Vector Field: F(x,y) = [{Vx}, {Vy}]\n"
|
| 313 |
+
output += f"Potential Function: φ(x,y) = {phi}\n\n"
|
| 314 |
+
|
| 315 |
+
# Show specific cases
|
| 316 |
+
output += "Specific Examples:\n"
|
| 317 |
+
examples = analyzer.get_specific_cases()
|
| 318 |
+
for case_name, (Vx_ex, Vy_ex, phi_ex) in examples.items():
|
| 319 |
+
output += f"{case_name}:\n"
|
| 320 |
+
output += f" F(x,y) = [{Vx_ex}, {Vy_ex}]\n"
|
| 321 |
+
output += f" φ(x,y) = {phi_ex}\n\n"
|
| 322 |
+
|
| 323 |
+
self.case1_output.setPlainText(output)
|
| 324 |
+
self.statusBar().showMessage('Symbolic Case 1 analysis completed')
|
| 325 |
+
|
| 326 |
+
except Exception as e:
|
| 327 |
+
self.case1_output.setPlainText(f"Error: {e}")
|
| 328 |
+
self.statusBar().showMessage(f'Error in symbolic Case 1 analysis: {e}')
|
| 329 |
+
|
| 330 |
+
def analyze_case2(self):
|
| 331 |
+
"""Analyze a specific Case 2 example"""
|
| 332 |
+
self.case2_output.clear()
|
| 333 |
+
|
| 334 |
+
try:
|
| 335 |
+
# Get parameters from UI
|
| 336 |
+
a1 = float(self.case2_a1.text())
|
| 337 |
+
b1 = float(self.case2_b1.text())
|
| 338 |
+
c1 = float(self.case2_c1.text())
|
| 339 |
+
a2 = float(self.case2_a2.text())
|
| 340 |
+
b2 = float(self.case2_b2.text())
|
| 341 |
+
c2 = float(self.case2_c2.text())
|
| 342 |
+
|
| 343 |
+
# Create analyzer
|
| 344 |
+
analyzer = LinearComponentField()
|
| 345 |
+
x, y = sp.symbols('x y')
|
| 346 |
+
|
| 347 |
+
# Create vector field
|
| 348 |
+
Vx = a1*x + b1*y + c1
|
| 349 |
+
Vy = a2*x + b2*y + c2
|
| 350 |
+
|
| 351 |
+
output = f"Case 2 Analysis:\n"
|
| 352 |
+
output += f"Vector Field: F(x,y) = [{Vx}, {Vy}]\n\n"
|
| 353 |
+
|
| 354 |
+
# Check gradient condition
|
| 355 |
+
is_gradient = analyzer.check_gradient_condition_for_specific(Vx, Vy)
|
| 356 |
+
output += f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x\n"
|
| 357 |
+
output += f" ∂P/∂y = {sp.diff(Vx, y)}\n"
|
| 358 |
+
output += f" ∂Q/∂x = {sp.diff(Vy, x)}\n"
|
| 359 |
+
output += f" Condition satisfied? {is_gradient}\n\n"
|
| 360 |
+
|
| 361 |
+
if is_gradient:
|
| 362 |
+
# Find potential
|
| 363 |
+
phi = analyzer.find_potential_for_specific_field(Vx, Vy)
|
| 364 |
+
output += f"Potential Function: φ(x,y) = {sp.simplify(phi)}\n\n"
|
| 365 |
+
|
| 366 |
+
# Verify
|
| 367 |
+
is_valid, diff_x, diff_y = analyzer.verify_potential(phi, Vx, Vy)
|
| 368 |
+
output += f"Verification: ∇φ = F? {is_valid}\n"
|
| 369 |
+
if not is_valid:
|
| 370 |
+
output += f" ∂φ/∂x - Vx = {diff_x}\n"
|
| 371 |
+
output += f" ∂φ/∂y - Vy = {diff_y}\n"
|
| 372 |
+
else:
|
| 373 |
+
output += "This field is not a gradient field. No potential function exists.\n"
|
| 374 |
+
|
| 375 |
+
self.case2_output.setPlainText(output)
|
| 376 |
+
self.statusBar().showMessage('Case 2 analysis completed')
|
| 377 |
+
|
| 378 |
+
except Exception as e:
|
| 379 |
+
self.case2_output.setPlainText(f"Error: {e}")
|
| 380 |
+
self.statusBar().showMessage(f'Error in Case 2 analysis: {e}')
|
| 381 |
+
|
| 382 |
+
def show_symbolic_case2(self):
|
| 383 |
+
"""Show symbolic analysis for Case 2"""
|
| 384 |
+
self.case2_output.clear()
|
| 385 |
+
|
| 386 |
+
try:
|
| 387 |
+
analyzer = LinearComponentField()
|
| 388 |
+
|
| 389 |
+
output = "Symbolic Analysis for Case 2:\n\n"
|
| 390 |
+
|
| 391 |
+
# Show gradient condition
|
| 392 |
+
condition = analyzer.get_gradient_condition()
|
| 393 |
+
output += f"Gradient Field Condition: {condition} = 0\n"
|
| 394 |
+
output += "This means: a₂ = b₁ for the general linear field\n\n"
|
| 395 |
+
|
| 396 |
+
# Show specific gradient cases
|
| 397 |
+
output += "Specific Gradient Cases:\n"
|
| 398 |
+
examples = analyzer.get_specific_gradient_cases()
|
| 399 |
+
for case_name, (Vx, Vy, phi) in examples.items():
|
| 400 |
+
output += f"{case_name}:\n"
|
| 401 |
+
output += f" F(x,y) = [{Vx}, {Vy}]\n"
|
| 402 |
+
output += f" φ(x,y) = {phi}\n\n"
|
| 403 |
+
|
| 404 |
+
# Show case study forms
|
| 405 |
+
output += "Exact Forms from Case Study:\n"
|
| 406 |
+
output_stream = io.StringIO()
|
| 407 |
+
with redirect_stdout(output_stream):
|
| 408 |
+
analyzer.demonstrate_case_study_forms()
|
| 409 |
+
output += output_stream.getvalue()
|
| 410 |
+
|
| 411 |
+
self.case2_output.setPlainText(output)
|
| 412 |
+
self.statusBar().showMessage('Symbolic Case 2 analysis completed')
|
| 413 |
+
|
| 414 |
+
except Exception as e:
|
| 415 |
+
self.case2_output.setPlainText(f"Error: {e}")
|
| 416 |
+
self.statusBar().showMessage(f'Error in symbolic Case 2 analysis: {e}')
|
| 417 |
+
|
| 418 |
+
def check_gradient_condition(self):
|
| 419 |
+
"""Check only the gradient condition for Case 2"""
|
| 420 |
+
try:
|
| 421 |
+
# Get parameters from UI
|
| 422 |
+
a1 = float(self.case2_a1.text())
|
| 423 |
+
b1 = float(self.case2_b1.text())
|
| 424 |
+
a2 = float(self.case2_a2.text())
|
| 425 |
+
|
| 426 |
+
# Check condition
|
| 427 |
+
condition_satisfied = abs(a2 - b1) < 1e-10 # Account for floating point errors
|
| 428 |
+
|
| 429 |
+
if condition_satisfied:
|
| 430 |
+
message = f"✓ Gradient condition satisfied: a₂ ({a2}) = b₁ ({b1})"
|
| 431 |
+
QMessageBox.information(self, "Gradient Condition", message)
|
| 432 |
+
else:
|
| 433 |
+
message = f"✗ Gradient condition not satisfied: a₂ ({a2}) ≠ b₁ ({b1})"
|
| 434 |
+
QMessageBox.warning(self, "Gradient Condition", message)
|
| 435 |
+
|
| 436 |
+
except Exception as e:
|
| 437 |
+
QMessageBox.critical(self, "Error", f"Error checking gradient condition: {e}")
|
| 438 |
+
|
| 439 |
+
def run_case1_examples(self):
|
| 440 |
+
"""Run Case 1 numerical examples"""
|
| 441 |
+
self.examples_output.clear()
|
| 442 |
+
|
| 443 |
+
output = io.StringIO()
|
| 444 |
+
with redirect_stdout(output):
|
| 445 |
+
try:
|
| 446 |
+
numerical_examples = NumericalExamples()
|
| 447 |
+
numerical_examples.run_case1_examples()
|
| 448 |
+
self.statusBar().showMessage('Case 1 examples completed')
|
| 449 |
+
except Exception as e:
|
| 450 |
+
print(f"Error running Case 1 examples: {e}")
|
| 451 |
+
self.statusBar().showMessage(f'Error: {e}')
|
| 452 |
+
|
| 453 |
+
self.examples_output.setPlainText(output.getvalue())
|
| 454 |
+
|
| 455 |
+
def run_case2_examples(self):
|
| 456 |
+
"""Run Case 2 numerical examples"""
|
| 457 |
+
self.examples_output.clear()
|
| 458 |
+
|
| 459 |
+
output = io.StringIO()
|
| 460 |
+
with redirect_stdout(output):
|
| 461 |
+
try:
|
| 462 |
+
numerical_examples = NumericalExamples()
|
| 463 |
+
numerical_examples.run_case2_examples()
|
| 464 |
+
self.statusBar().showMessage('Case 2 examples completed')
|
| 465 |
+
except Exception as e:
|
| 466 |
+
print(f"Error running Case 2 examples: {e}")
|
| 467 |
+
self.statusBar().showMessage(f'Error: {e}')
|
| 468 |
+
|
| 469 |
+
self.examples_output.setPlainText(output.getvalue())
|
| 470 |
+
|
| 471 |
+
def run_all_examples(self):
|
| 472 |
+
"""Run all numerical examples"""
|
| 473 |
+
self.examples_output.clear()
|
| 474 |
+
|
| 475 |
+
output = io.StringIO()
|
| 476 |
+
with redirect_stdout(output):
|
| 477 |
+
try:
|
| 478 |
+
numerical_examples = NumericalExamples()
|
| 479 |
+
print("=" * 60)
|
| 480 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 481 |
+
print("=" * 60)
|
| 482 |
+
numerical_examples.run_case1_examples()
|
| 483 |
+
numerical_examples.run_case2_examples()
|
| 484 |
+
self.statusBar().showMessage('All examples completed')
|
| 485 |
+
except Exception as e:
|
| 486 |
+
print(f"Error running examples: {e}")
|
| 487 |
+
self.statusBar().showMessage(f'Error: {e}')
|
| 488 |
+
|
| 489 |
+
self.examples_output.setPlainText(output.getvalue())
|
| 490 |
+
|
| 491 |
+
|
| 492 |
+
def main():
|
| 493 |
+
app = QApplication(sys.argv)
|
| 494 |
+
|
| 495 |
+
# Set application style
|
| 496 |
+
app.setStyle('Fusion')
|
| 497 |
+
|
| 498 |
+
# Create and show the main window
|
| 499 |
+
window = GradientFieldApp()
|
| 500 |
+
window.show()
|
| 501 |
+
|
| 502 |
+
# Start the event loop
|
| 503 |
+
sys.exit(app.exec_())
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
if __name__ == '__main__':
|
| 507 |
+
main()
|
constructing_gradient_fields_case_1_and_2_optimized_final.py
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sympy as sp
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import Tuple, Union, Dict, List
|
| 4 |
+
|
| 5 |
+
class GradientFieldAnalyzer(ABC):
|
| 6 |
+
"""Abstract base class for analyzing gradient fields"""
|
| 7 |
+
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.x, self.y = sp.symbols('x y', real=True)
|
| 10 |
+
self.X = sp.Matrix([self.x, self.y])
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def find_potential(self) -> sp.Expr:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def verify_potential(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr) -> Tuple[bool, sp.Expr, sp.Expr]:
|
| 21 |
+
"""Verify that phi is indeed a potential function for the vector field"""
|
| 22 |
+
grad_x = sp.diff(phi, self.x)
|
| 23 |
+
grad_y = sp.diff(phi, self.y)
|
| 24 |
+
|
| 25 |
+
diff_x = sp.simplify(grad_x - Vx)
|
| 26 |
+
diff_y = sp.simplify(grad_y - Vy)
|
| 27 |
+
|
| 28 |
+
is_valid = diff_x.equals(0) and diff_y.equals(0)
|
| 29 |
+
return is_valid, diff_x, diff_y
|
| 30 |
+
|
| 31 |
+
def display_results(self, phi: sp.Expr, Vx: sp.Expr, Vy: sp.Expr):
|
| 32 |
+
"""Display the results in a formatted way"""
|
| 33 |
+
print(f"Vector Field: F(x,y) = [{Vx}, {Vy}]")
|
| 34 |
+
print(f"Potential Function: φ(x,y) = {sp.simplify(phi)}")
|
| 35 |
+
|
| 36 |
+
is_valid, diff_x, diff_y = self.verify_potential(phi, Vx, Vy)
|
| 37 |
+
print(f"Verification: ∇φ = F? {is_valid}")
|
| 38 |
+
if not is_valid:
|
| 39 |
+
print(f" ∂φ/∂x - Vx = {diff_x}")
|
| 40 |
+
print(f" ∂φ/∂y - Vy = {diff_y}")
|
| 41 |
+
print("-" * 50)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class ConstantComponentField(GradientFieldAnalyzer):
|
| 45 |
+
"""Case 1: One component is constant"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, constant_component: str = 'x', constant_value: sp.Symbol = None):
|
| 48 |
+
super().__init__()
|
| 49 |
+
self.constant_component = constant_component.lower()
|
| 50 |
+
self.c = constant_value if constant_value else sp.Symbol('c', real=True)
|
| 51 |
+
|
| 52 |
+
# Define unknown functions
|
| 53 |
+
self.F_x = sp.Function('F_x')(self.x)
|
| 54 |
+
self.F_y = sp.Function('F_y')(self.y)
|
| 55 |
+
|
| 56 |
+
def find_potential(self) -> sp.Expr:
|
| 57 |
+
if self.constant_component == 'x':
|
| 58 |
+
# F(x,y) = [c, F_y(y)]
|
| 59 |
+
phi = self.c * self.x + sp.Integral(self.F_y, self.y)
|
| 60 |
+
else: # constant y-component
|
| 61 |
+
# F(x,y) = [F_x(x), c]
|
| 62 |
+
phi = self.c * self.y + sp.Integral(self.F_x, self.x)
|
| 63 |
+
|
| 64 |
+
return phi
|
| 65 |
+
|
| 66 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 67 |
+
if self.constant_component == 'x':
|
| 68 |
+
return self.c, self.F_y
|
| 69 |
+
else:
|
| 70 |
+
return self.F_x, self.c
|
| 71 |
+
|
| 72 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 73 |
+
"""Find potential for a specific vector field (not symbolic)"""
|
| 74 |
+
# Check which component is constant
|
| 75 |
+
Vx_free_symbols = Vx.free_symbols if hasattr(Vx, 'free_symbols') else set()
|
| 76 |
+
Vy_free_symbols = Vy.free_symbols if hasattr(Vy, 'free_symbols') else set()
|
| 77 |
+
|
| 78 |
+
Vx_depends_on_x = self.x in Vx_free_symbols
|
| 79 |
+
Vx_depends_on_y = self.y in Vx_free_symbols
|
| 80 |
+
Vy_depends_on_x = self.x in Vy_free_symbols
|
| 81 |
+
Vy_depends_on_y = self.y in Vy_free_symbols
|
| 82 |
+
|
| 83 |
+
if not Vx_depends_on_x and not Vx_depends_on_y: # Vx is constant
|
| 84 |
+
# F(x,y) = [constant, Vy]
|
| 85 |
+
phi = Vx * self.x + sp.integrate(Vy, self.y)
|
| 86 |
+
elif not Vy_depends_on_x and not Vy_depends_on_y: # Vy is constant
|
| 87 |
+
# F(x,y) = [Vx, constant]
|
| 88 |
+
phi = Vy * self.y + sp.integrate(Vx, self.x)
|
| 89 |
+
else:
|
| 90 |
+
raise ValueError("One component must be constant (independent of both x and y)")
|
| 91 |
+
return sp.simplify(phi)
|
| 92 |
+
|
| 93 |
+
def get_specific_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 94 |
+
"""Return specific cases with actual functions"""
|
| 95 |
+
cases = {}
|
| 96 |
+
|
| 97 |
+
# Case 1a: Constant x-component with specific F_y
|
| 98 |
+
F_y_specific = self.y**2 # Example: F_y(y) = y²
|
| 99 |
+
Vx1 = 2 # Use a numeric constant instead of symbolic c
|
| 100 |
+
Vy1 = F_y_specific
|
| 101 |
+
phi1 = self.find_potential_for_specific_field(Vx1, Vy1)
|
| 102 |
+
cases['constant_x_component'] = (Vx1, Vy1, phi1)
|
| 103 |
+
|
| 104 |
+
# Case 1b: Constant y-component with specific F_x
|
| 105 |
+
F_x_specific = sp.sin(self.x) # Example: F_x(x) = sin(x)
|
| 106 |
+
Vx2 = F_x_specific
|
| 107 |
+
Vy2 = 3 # Use a numeric constant instead of symbolic c
|
| 108 |
+
phi2 = self.find_potential_for_specific_field(Vx2, Vy2)
|
| 109 |
+
cases['constant_y_component'] = (Vx2, Vy2, phi2)
|
| 110 |
+
|
| 111 |
+
return cases
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class LinearComponentField(GradientFieldAnalyzer):
|
| 115 |
+
"""Case 2: Both components are linear functions"""
|
| 116 |
+
|
| 117 |
+
def __init__(self):
|
| 118 |
+
super().__init__()
|
| 119 |
+
# Define constants for linear functions
|
| 120 |
+
self.a1, self.b1, self.c1 = sp.symbols('a1 b1 c1', real=True)
|
| 121 |
+
self.a2, self.b2, self.c2 = sp.symbols('a2 b2 c2', real=True)
|
| 122 |
+
|
| 123 |
+
def find_potential(self, Vx: sp.Expr = None, Vy: sp.Expr = None) -> sp.Expr:
|
| 124 |
+
"""Find potential function for given linear vector field"""
|
| 125 |
+
if Vx is None or Vy is None:
|
| 126 |
+
Vx, Vy = self.get_general_linear_field()
|
| 127 |
+
|
| 128 |
+
# Use the robust method
|
| 129 |
+
phi = self._find_potential_robust(Vx, Vy)
|
| 130 |
+
return phi
|
| 131 |
+
|
| 132 |
+
def _find_potential_robust(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 133 |
+
"""Robust implementation that properly checks gradient condition"""
|
| 134 |
+
# First check if it's a gradient field
|
| 135 |
+
if not self.check_gradient_condition_for_specific(Vx, Vy):
|
| 136 |
+
raise ValueError("Field is not a gradient field - cannot find potential")
|
| 137 |
+
|
| 138 |
+
# Method: Integrate Vx with respect to x
|
| 139 |
+
phi_x = sp.integrate(Vx, self.x)
|
| 140 |
+
|
| 141 |
+
# Differentiate with respect to y and compare with Vy
|
| 142 |
+
diff_phi_x_y = sp.diff(phi_x, self.y)
|
| 143 |
+
remaining = Vy - diff_phi_x_y
|
| 144 |
+
|
| 145 |
+
# CRITICAL: Check if remaining depends only on y
|
| 146 |
+
remaining_free_symbols = remaining.free_symbols if hasattr(remaining, 'free_symbols') else set()
|
| 147 |
+
if self.x in remaining_free_symbols:
|
| 148 |
+
raise ValueError(f"Field is not a gradient field - remaining term depends on x: {remaining}")
|
| 149 |
+
|
| 150 |
+
# Integrate remaining with respect to y
|
| 151 |
+
phi_y = sp.integrate(remaining, self.y)
|
| 152 |
+
|
| 153 |
+
# Combine results
|
| 154 |
+
phi = phi_x + phi_y
|
| 155 |
+
|
| 156 |
+
return sp.simplify(phi)
|
| 157 |
+
|
| 158 |
+
def get_vector_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 159 |
+
return self.get_general_linear_field()
|
| 160 |
+
|
| 161 |
+
def get_general_linear_field(self) -> Tuple[sp.Expr, sp.Expr]:
|
| 162 |
+
"""General linear vector field: F(x,y) = [a1*x + b1*y + c1, a2*x + b2*y + c2]"""
|
| 163 |
+
Vx = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 164 |
+
Vy = self.a2 * self.x + self.b2 * self.y + self.c2
|
| 165 |
+
return Vx, Vy
|
| 166 |
+
|
| 167 |
+
def get_gradient_condition(self) -> sp.Expr:
|
| 168 |
+
"""Return the condition for the field to be a gradient field"""
|
| 169 |
+
# For a 2D field F = [P, Q] to be a gradient field, we need ∂P/∂y = ∂Q/∂x
|
| 170 |
+
Vx, Vy = self.get_general_linear_field()
|
| 171 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 172 |
+
return sp.simplify(condition)
|
| 173 |
+
|
| 174 |
+
def find_potential_for_specific_field(self, Vx: sp.Expr, Vy: sp.Expr) -> sp.Expr:
|
| 175 |
+
"""Find potential for a specific numeric vector field using robust integration"""
|
| 176 |
+
return self._find_potential_robust(Vx, Vy)
|
| 177 |
+
|
| 178 |
+
def get_specific_gradient_cases(self) -> Dict[str, Tuple[sp.Expr, sp.Expr, sp.Expr]]:
|
| 179 |
+
"""Return specific cases where the field is a gradient field"""
|
| 180 |
+
cases = {}
|
| 181 |
+
|
| 182 |
+
# Case 2a: a2 = b1 (from the case study) - FORM 1
|
| 183 |
+
Vx1 = self.a1 * self.x + self.b1 * self.y + self.c1
|
| 184 |
+
Vy1 = self.b1 * self.x + self.b2 * self.y + self.c2 # a2 = b1
|
| 185 |
+
phi1 = self._find_potential_robust(Vx1, Vy1)
|
| 186 |
+
cases['symmetric_case_1'] = (Vx1, Vy1, phi1)
|
| 187 |
+
|
| 188 |
+
# Case 2b: b2 = a1 (alternative symmetric case) - FORM 2
|
| 189 |
+
Vx2 = self.a2 * self.x + self.a1 * self.y + self.c2 # swapped coefficients
|
| 190 |
+
Vy2 = self.a1 * self.x + self.b1 * self.y + self.c1 # b2 = a1
|
| 191 |
+
phi2 = self._find_potential_robust(Vx2, Vy2)
|
| 192 |
+
cases['symmetric_case_2'] = (Vx2, Vy2, phi2)
|
| 193 |
+
|
| 194 |
+
return cases
|
| 195 |
+
|
| 196 |
+
def check_gradient_condition_for_specific(self, Vx: sp.Expr, Vy: sp.Expr) -> bool:
|
| 197 |
+
"""Check if a specific vector field satisfies the gradient condition"""
|
| 198 |
+
condition = sp.diff(Vx, self.y) - sp.diff(Vy, self.x)
|
| 199 |
+
return sp.simplify(condition).equals(0)
|
| 200 |
+
|
| 201 |
+
def demonstrate_case_study_forms(self):
|
| 202 |
+
"""Demonstrate the exact forms from the case study"""
|
| 203 |
+
print("\nEXACT FORMS FROM CASE STUDY:")
|
| 204 |
+
print("=" * 50)
|
| 205 |
+
|
| 206 |
+
# Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)
|
| 207 |
+
print("Form 1 (Equation 3.44):")
|
| 208 |
+
phi_form1 = self.a1 * self.x**2 / 2 + self.b2 * self.y**2 / 2 + self.c2 * self.y + self.x * (self.b1 * self.y + self.c1)
|
| 209 |
+
print(f"φ(x,y) = {phi_form1}")
|
| 210 |
+
|
| 211 |
+
Vx_form1 = sp.diff(phi_form1, self.x)
|
| 212 |
+
Vy_form1 = sp.diff(phi_form1, self.y)
|
| 213 |
+
print(f"F(x,y) = ∇φ = [{Vx_form1}, {Vy_form1}]")
|
| 214 |
+
matches_form1 = Vx_form1.equals(self.a1*self.x + self.b1*self.y + self.c1) and Vy_form1.equals(self.b1*self.x + self.b2*self.y + self.c2)
|
| 215 |
+
print(f"Matches Form 1 from case study: {matches_form1}")
|
| 216 |
+
print()
|
| 217 |
+
|
| 218 |
+
# Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)
|
| 219 |
+
print("Form 2 (Equation 3.44):")
|
| 220 |
+
phi_form2 = self.a2 * self.x**2 / 2 + self.b1 * self.y**2 / 2 + self.c1 * self.y + self.x * (self.a1 * self.y + self.c2)
|
| 221 |
+
print(f"φ(x,y) = {phi_form2}")
|
| 222 |
+
|
| 223 |
+
Vx_form2 = sp.diff(phi_form2, self.x)
|
| 224 |
+
Vy_form2 = sp.diff(phi_form2, self.y)
|
| 225 |
+
print(f"F(x,y) = ∇φ = [{Vx_form2}, {Vy_form2}]")
|
| 226 |
+
matches_form2 = Vx_form2.equals(self.a2*self.x + self.a1*self.y + self.c2) and Vy_form2.equals(self.a1*self.x + self.b1*self.y + self.c1)
|
| 227 |
+
print(f"Matches Form 2 from case study: {matches_form2}")
|
| 228 |
+
print()
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
class GradientFieldFactory:
|
| 232 |
+
"""Factory class to create different types of gradient fields"""
|
| 233 |
+
|
| 234 |
+
@staticmethod
|
| 235 |
+
def create_constant_component_field(component: str = 'x', constant: sp.Symbol = None):
|
| 236 |
+
return ConstantComponentField(component, constant)
|
| 237 |
+
|
| 238 |
+
@staticmethod
|
| 239 |
+
def create_linear_component_field():
|
| 240 |
+
return LinearComponentField()
|
| 241 |
+
|
| 242 |
+
@staticmethod
|
| 243 |
+
def analyze_all_cases():
|
| 244 |
+
"""Analyze all cases from the case study"""
|
| 245 |
+
print("=" * 60)
|
| 246 |
+
print("GRADIENT FIELD CASE STUDY ANALYSIS")
|
| 247 |
+
print("=" * 60)
|
| 248 |
+
|
| 249 |
+
# Case 1: Constant component
|
| 250 |
+
print("\nCASE 1: One Component is Constant")
|
| 251 |
+
print("-" * 40)
|
| 252 |
+
|
| 253 |
+
case1_x = GradientFieldFactory.create_constant_component_field('x')
|
| 254 |
+
phi1 = case1_x.find_potential()
|
| 255 |
+
Vx1, Vy1 = case1_x.get_vector_field()
|
| 256 |
+
case1_x.display_results(phi1, Vx1, Vy1)
|
| 257 |
+
|
| 258 |
+
case1_y = GradientFieldFactory.create_constant_component_field('y')
|
| 259 |
+
phi2 = case1_y.find_potential()
|
| 260 |
+
Vx2, Vy2 = case1_y.get_vector_field()
|
| 261 |
+
case1_y.display_results(phi2, Vx2, Vy2)
|
| 262 |
+
|
| 263 |
+
# Show specific examples
|
| 264 |
+
print("\nSpecific Examples for Case 1:")
|
| 265 |
+
examples1 = case1_x.get_specific_cases()
|
| 266 |
+
for case_name, (Vx, Vy, phi) in examples1.items():
|
| 267 |
+
print(f"{case_name}:")
|
| 268 |
+
case1_x.display_results(phi, Vx, Vy)
|
| 269 |
+
|
| 270 |
+
# Case 2: Linear components
|
| 271 |
+
print("\nCASE 2: Both Components are Linear")
|
| 272 |
+
print("-" * 40)
|
| 273 |
+
|
| 274 |
+
case2 = GradientFieldFactory.create_linear_component_field()
|
| 275 |
+
|
| 276 |
+
# Show gradient condition
|
| 277 |
+
condition = case2.get_gradient_condition()
|
| 278 |
+
print(f"Gradient Field Condition: ∂P/∂y = ∂Q/∂x → {condition} = 0")
|
| 279 |
+
print(f"This means: a2 = b1 for the general linear field")
|
| 280 |
+
|
| 281 |
+
# Show specific gradient cases
|
| 282 |
+
print("\nSpecific Gradient Cases for Case 2:")
|
| 283 |
+
examples2 = case2.get_specific_gradient_cases()
|
| 284 |
+
for case_name, (Vx, Vy, phi) in examples2.items():
|
| 285 |
+
print(f"{case_name}:")
|
| 286 |
+
case2.display_results(phi, Vx, Vy)
|
| 287 |
+
|
| 288 |
+
# Demonstrate the exact forms from the case study
|
| 289 |
+
case2.demonstrate_case_study_forms()
|
| 290 |
+
|
| 291 |
+
return case1_x, case1_y, case2
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
class NumericalExamples:
|
| 295 |
+
"""Class to demonstrate and verify numerical examples"""
|
| 296 |
+
|
| 297 |
+
def __init__(self):
|
| 298 |
+
self.x, self.y = sp.symbols('x y')
|
| 299 |
+
self.constant_analyzer = ConstantComponentField()
|
| 300 |
+
self.linear_analyzer = LinearComponentField()
|
| 301 |
+
|
| 302 |
+
def run_case1_examples(self):
|
| 303 |
+
"""Run numerical examples for Case 1"""
|
| 304 |
+
print("\nNumerical Examples - Case 1:")
|
| 305 |
+
print("-" * 40)
|
| 306 |
+
|
| 307 |
+
# Example 1: F(x,y) = [2, 3y²]
|
| 308 |
+
print("Example 1: F(x,y) = [2, 3y²]")
|
| 309 |
+
Vx1 = 2
|
| 310 |
+
Vy1 = 3 * self.y**2
|
| 311 |
+
phi1 = self.constant_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 312 |
+
self.constant_analyzer.display_results(phi1, Vx1, Vy1)
|
| 313 |
+
|
| 314 |
+
# Example 2: F(x,y) = [sin(x), 5]
|
| 315 |
+
print("Example 2: F(x,y) = [sin(x), 5]")
|
| 316 |
+
Vx2 = sp.sin(self.x)
|
| 317 |
+
Vy2 = 5
|
| 318 |
+
phi2 = self.constant_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 319 |
+
self.constant_analyzer.display_results(phi2, Vx2, Vy2)
|
| 320 |
+
|
| 321 |
+
def run_case2_examples(self):
|
| 322 |
+
"""Run numerical examples for Case 2"""
|
| 323 |
+
print("\nNumerical Examples - Case 2:")
|
| 324 |
+
print("-" * 40)
|
| 325 |
+
|
| 326 |
+
# Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2] where a2 = b1 = 3
|
| 327 |
+
print("Example 1: F(x,y) = [2x + 3y + 1, 3x + 4y + 2]")
|
| 328 |
+
Vx1 = 2*self.x + 3*self.y + 1
|
| 329 |
+
Vy1 = 3*self.x + 4*self.y + 2
|
| 330 |
+
|
| 331 |
+
# Check if it's a gradient field first
|
| 332 |
+
is_gradient = self.linear_analyzer.check_gradient_condition_for_specific(Vx1, Vy1)
|
| 333 |
+
print(f"Is this a gradient field? {is_gradient}")
|
| 334 |
+
|
| 335 |
+
if is_gradient:
|
| 336 |
+
phi1 = self.linear_analyzer.find_potential_for_specific_field(Vx1, Vy1)
|
| 337 |
+
self.linear_analyzer.display_results(phi1, Vx1, Vy1)
|
| 338 |
+
|
| 339 |
+
# Show that this matches Form 1 from the case study
|
| 340 |
+
print("This matches Form 1 from case study with:")
|
| 341 |
+
print(" a1 = 2, b1 = 3, c1 = 1")
|
| 342 |
+
print(" a2 = 3, b2 = 4, c2 = 2")
|
| 343 |
+
phi_form1 = 2*self.x**2/2 + 4*self.y**2/2 + 2*self.y + self.x*(3*self.y + 1)
|
| 344 |
+
print(f" φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 345 |
+
print(f" = {sp.simplify(phi_form1)}")
|
| 346 |
+
else:
|
| 347 |
+
print("This field cannot have a potential function!")
|
| 348 |
+
print("-" * 50)
|
| 349 |
+
|
| 350 |
+
# Example 2: F(x,y) = [x + 2y, 2x + 3y] where a2 = b1 = 2
|
| 351 |
+
print("Example 2: F(x,y) = [x + 2y, 2x + 3y]")
|
| 352 |
+
Vx2 = self.x + 2*self.y
|
| 353 |
+
Vy2 = 2*self.x + 3*self.y
|
| 354 |
+
|
| 355 |
+
is_gradient2 = self.linear_analyzer.check_gradient_condition_for_specific(Vx2, Vy2)
|
| 356 |
+
print(f"Is this a gradient field? {is_gradient2}")
|
| 357 |
+
|
| 358 |
+
if is_gradient2:
|
| 359 |
+
phi2 = self.linear_analyzer.find_potential_for_specific_field(Vx2, Vy2)
|
| 360 |
+
self.linear_analyzer.display_results(phi2, Vx2, Vy2)
|
| 361 |
+
else:
|
| 362 |
+
print("This field cannot have a potential function!")
|
| 363 |
+
print("-" * 50)
|
| 364 |
+
|
| 365 |
+
# Example 3: Non-gradient field (should fail)
|
| 366 |
+
print("Example 3: Non-gradient field F(x,y) = [x + y, 2x + y]")
|
| 367 |
+
Vx3 = self.x + self.y
|
| 368 |
+
Vy3 = 2*self.x + self.y
|
| 369 |
+
|
| 370 |
+
is_gradient3 = self.linear_analyzer.check_gradient_condition_for_specific(Vx3, Vy3)
|
| 371 |
+
print(f"Is this a gradient field? {is_gradient3}")
|
| 372 |
+
|
| 373 |
+
if is_gradient3:
|
| 374 |
+
print("ERROR: This should not be a gradient field!")
|
| 375 |
+
print("Let's check the gradient condition:")
|
| 376 |
+
print(f" ∂P/∂y = {sp.diff(Vx3, self.y)}")
|
| 377 |
+
print(f" ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 378 |
+
print(f" Gradient condition satisfied? {sp.diff(Vx3, self.y).equals(sp.diff(Vy3, self.x))}")
|
| 379 |
+
else:
|
| 380 |
+
print("✓ Correct: This field cannot have a potential function!")
|
| 381 |
+
print(f"Reason: ∂P/∂y = {sp.diff(Vx3, self.y)} ≠ ∂Q/∂x = {sp.diff(Vy3, self.x)}")
|
| 382 |
+
print("The gradient condition a₂ = b₁ is not satisfied")
|
| 383 |
+
|
| 384 |
+
# Test our robust integration method
|
| 385 |
+
print("\nTesting robust integration method:")
|
| 386 |
+
try:
|
| 387 |
+
phi3 = self.linear_analyzer.find_potential_for_specific_field(Vx3, Vy3)
|
| 388 |
+
print(f"Unexpected success: φ(x,y) = {phi3}")
|
| 389 |
+
except ValueError as e:
|
| 390 |
+
print(f"✓ Robust method correctly failed: {e}")
|
| 391 |
+
print("-" * 50)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def main():
|
| 395 |
+
# Run the complete analysis
|
| 396 |
+
analyzer1, analyzer2, analyzer3 = GradientFieldFactory.analyze_all_cases()
|
| 397 |
+
|
| 398 |
+
# Run numerical examples
|
| 399 |
+
print("\n" + "=" * 60)
|
| 400 |
+
print("NUMERICAL EXAMPLES VERIFICATION")
|
| 401 |
+
print("=" * 60)
|
| 402 |
+
|
| 403 |
+
numerical_examples = NumericalExamples()
|
| 404 |
+
numerical_examples.run_case1_examples()
|
| 405 |
+
numerical_examples.run_case2_examples()
|
| 406 |
+
|
| 407 |
+
# Final summary
|
| 408 |
+
print("\n" + "=" * 60)
|
| 409 |
+
print("FINAL SUMMARY: CONSTRUCTING GRADIENT FIELDS")
|
| 410 |
+
print("=" * 60)
|
| 411 |
+
|
| 412 |
+
print("\n✓ CASE 1: One Component Constant")
|
| 413 |
+
print(" • Always a gradient field")
|
| 414 |
+
print(" • Potential: φ(x,y) = c·x + ∫Fᵧ(y)dy OR φ(x,y) = c·y + ∫Fₓ(x)dx")
|
| 415 |
+
|
| 416 |
+
print("\n✓ CASE 2: Both Components Linear")
|
| 417 |
+
print(" • Gradient field IF AND ONLY IF a₂ = b₁")
|
| 418 |
+
print(" • Potential forms:")
|
| 419 |
+
print(" Form 1: φ(x,y) = a₁x²/2 + b₂y²/2 + c₂y + x(b₁y + c₁)")
|
| 420 |
+
print(" Form 2: φ(x,y) = a₂x²/2 + b₁y²/2 + c₁y + x(a₁y + c₂)")
|
| 421 |
+
|
| 422 |
+
print("\n✓ KEY MATHEMATICAL INSIGHTS:")
|
| 423 |
+
print(" • Gradient condition: ∂P/∂y = ∂Q/∂x")
|
| 424 |
+
print(" • For linear fields: a₂ = b₁")
|
| 425 |
+
print(" • Robust integration method prevents false positives")
|
| 426 |
+
print(" • Matches case study equations (3.44) and (3.45)")
|
| 427 |
+
|
| 428 |
+
print("\n✓ OBJECT-ORIENTED DESIGN:")
|
| 429 |
+
print(" • Abstract base class for extensibility")
|
| 430 |
+
print(" • Factory pattern for creating analyzers")
|
| 431 |
+
print(" • Robust verification and error handling")
|
| 432 |
+
print(" • Clear separation of symbolic and numerical analysis")
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
if __name__ == "__main__":
|
| 436 |
+
main()
|
output.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5f83877946e2d1de9ab20ec56f24cec366a311a81e80dfdc63dfdb9d4a0e13c8
|
| 3 |
+
size 11969088
|
requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
PyQt5
|