TroglodyteDerivations commited on
Commit
4f1509e
·
verified ·
1 Parent(s): 8aaac83

Upload 22 files

Browse files
Files changed (23) hide show
  1. .gitattributes +5 -0
  2. Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.24 PM.png +3 -0
  3. Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.37 PM.png +3 -0
  4. Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.50 PM.png +3 -0
  5. Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.50.16 PM.png +3 -0
  6. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_000.py +247 -0
  7. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_001.py +318 -0
  8. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_002.py +326 -0
  9. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_003.py +374 -0
  10. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_004.py +405 -0
  11. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_005.py +400 -0
  12. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_006.py +412 -0
  13. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_007.py +421 -0
  14. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_008.py +451 -0
  15. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_009.py +424 -0
  16. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_final.py +436 -0
  17. Case Study Constructing Gradient Fields/constructing_gradient_fields_case_1_and_2_optimized_final.py +436 -0
  18. Case Study Constructing Gradient Fields/requirements.txt +1 -0
  19. Case Study Constructing Gradient Fields/test_corrected_examples.py +43 -0
  20. app.py +507 -0
  21. constructing_gradient_fields_case_1_and_2_optimized_final.py +436 -0
  22. output.mp4 +3 -0
  23. 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

  • SHA256: df59e3207c777c151d9b34ed500972f68dc33f86029350c3c23944ff2d18ebda
  • Pointer size: 131 Bytes
  • Size of remote file: 249 kB
Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.37 PM.png ADDED

Git LFS Details

  • SHA256: b4201e65949c1cfb4cb21f9fafe812d2291f1d99549f73c4b266cb7c09d8876d
  • Pointer size: 131 Bytes
  • Size of remote file: 286 kB
Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.49.50 PM.png ADDED

Git LFS Details

  • SHA256: 6bc1312378220b286374bcf9740c6babb09cc826fec0a69886513f41f3b18200
  • Pointer size: 131 Bytes
  • Size of remote file: 249 kB
Case Study Constructing Gradient Fields/Screenshot 2025-11-20 at 12.50.16 PM.png ADDED

Git LFS Details

  • SHA256: fdd1077b88a82ab92843fffc12c9131f8920690296895392eed6707aafd1cc6c
  • Pointer size: 131 Bytes
  • Size of remote file: 125 kB
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