Skip to content

Commit 03150a2

Browse files
authored
FEATURE: 2d heateq to pdesolver pkg (#72)
* 2d heat equation pde class implemented * sync up * sync up * sync up * explicit solver implemented * sync up * sync up * sync up * added test suite and validation
1 parent cc7225a commit 03150a2

File tree

13 files changed

+1201
-104
lines changed

13 files changed

+1201
-104
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ pdesolvers.egg-info/
2424
# Other files
2525
*.log
2626
/.coverage
27+
*.gif
28+
*.csv

null

1022 Bytes
Binary file not shown.

pdesolvers/main.py

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,58 @@ def main():
1515
# solver1 = pde.Heat1DCNSolver(equation1)
1616
# solver2 = pde.Heat1DExplicitSolver(equation1)
1717

18-
# testing for monte carlo pricing
18+
# testing 2d heat equation
19+
20+
xLength = 10 # Lx
21+
yLength = 10 # Ly
22+
maxTime = 0.5 # tmax
23+
diffusivityConstant = 4 # kappa
24+
numPointsSpace = 50 # x_points = y_points
25+
numPointsTime = 2000 # t_points
26+
27+
equation = (pde.HeatEquation2D(maxTime, numPointsTime, diffusivityConstant, xLength, numPointsSpace))
28+
equation.set_initial_temp(lambda x, y: 10 * np.exp(-((x - xLength/2)**2 + (y - yLength/2)**2) / 2))
29+
equation.set_right_boundary_temp(lambda t, y: 20 + 100 * y * (yLength - y)**3 * (t - 1)**2 * (t > 1))
30+
equation.set_left_boundary_temp(lambda t, y: 20 + 10 * y * (yLength - y) * t**2)
31+
equation.set_top_boundary_temp(lambda t, x: 20 + 5 * x * (xLength - x) * t**4)
32+
equation.set_bottom_boundary_temp(lambda t, x: 20)
1933

20-
ticker = 'AAPL'
34+
solver1 = pde.Heat2DExplicitSolver(equation)
35+
solver1 = pde.Heat2DCNSolver(equation)
36+
solution1 = solver1.solve()
37+
solution1.animate(filename="Explicit")
38+
solver2 = pde.Heat2DCNSolver(equation)
39+
solution2 = solver2.solve()
40+
solution2.animate(filename="Crank-Nicolson")
41+
42+
# testing for monte carlo pricing
43+
# ticker = 'AAPL'
2144

22-
# STOCK
23-
historical_data = pde.HistoricalStockData(ticker)
24-
historical_data.fetch_stock_data( "2024-03-21","2025-03-21")
25-
sigma, r = historical_data.estimate_metrics()
26-
current_price = historical_data.get_latest_stock_price()
45+
# # STOCK
46+
# historical_data = pde.HistoricalStockData(ticker)
47+
# historical_data.fetch_stock_data( "2024-03-21","2025-03-21")
48+
# sigma, r = historical_data.estimate_metrics()
49+
# current_price = historical_data.get_latest_stock_price()
2750

28-
equation2 = pde.BlackScholesEquation(pde.OptionType.EUROPEAN_CALL, current_price, 100, r, sigma, 1, 100, 20000)
51+
# equation2 = pde.BlackScholesEquation(pde.OptionType.EUROPEAN_CALL, current_price, 100, r, sigma, 1, 100, 20000)
2952

30-
solver1 = pde.BlackScholesCNSolver(equation2)
31-
solver2 = pde.BlackScholesExplicitSolver(equation2)
32-
sol1 = solver1.solve()
33-
sol1.plot()
53+
# solver1 = pde.BlackScholesCNSolver(equation2)
54+
# solver2 = pde.BlackScholesExplicitSolver(equation2)
55+
# sol1 = solver1.solve()
56+
# sol1.plot()
3457

35-
# COMPARISON
36-
# look to see the corresponding option price for the expiration date and strike price
37-
pricing_1 = pde.BlackScholesFormula(pde.OptionType.EUROPEAN_CALL, current_price, 100, r, sigma, 1)
38-
pricing_2 = pde.MonteCarloPricing(pde.OptionType.EUROPEAN_CALL, current_price, 100, r, sigma, 1, 365, 1000)
58+
# # COMPARISON
59+
# # look to see the corresponding option price for the expiration date and strike price
60+
# pricing_1 = pde.BlackScholesFormula(pde.OptionType.EUROPEAN_CALL, current_price, 100, r, sigma, 1)
61+
# pricing_2 = pde.MonteCarloPricing(pde.OptionType.EUROPEAN_CALL, current_price, 100, r, sigma, 1, 365, 1000)
3962

40-
bs_price = pricing_1.get_black_scholes_merton_price()
41-
monte_carlo_price = pricing_2.get_monte_carlo_option_price()
63+
# bs_price = pricing_1.get_black_scholes_merton_price()
64+
# monte_carlo_price = pricing_2.get_monte_carlo_option_price()
4265

43-
pde_price = sol1.get_result()[-1, 0]
44-
print(f"PDE Price: {pde_price}")
45-
print(f"Black-Scholes Price: {bs_price}")
46-
print(f"Monte-Carlo Price: {monte_carlo_price}")
66+
# pde_price = sol1.get_result()[-1, 0]
67+
# print(f"PDE Price: {pde_price}")
68+
# print(f"Black-Scholes Price: {bs_price}")
69+
# print(f"Monte-Carlo Price: {monte_carlo_price}")
4770

4871
if __name__ == "__main__":
4972
main()

pdesolvers/pdes/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .heat_1d import HeatEquation
2-
from .black_scholes import BlackScholesEquation
2+
from .black_scholes import BlackScholesEquation
3+
from .heat_2d import HeatEquation2D

pdesolvers/pdes/heat_2d.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import numpy as np
2+
3+
class HeatEquation2D:
4+
def __init__(self, time, t_nodes, k, xlength, x_nodes, ylength=None, y_nodes=None):
5+
6+
assert time > 0, "Time must be positive"
7+
assert t_nodes > 1, "Number of time nodes must be greater than 1"
8+
assert k > 0, "Diffusivity constant k must be positive"
9+
assert xlength > 0, "X-length must be positive"
10+
assert x_nodes > 2, "Number of x nodes must be greater than 2"
11+
if ylength is not None:
12+
assert ylength > 0, "Y-length must be positive"
13+
if y_nodes is not None:
14+
assert y_nodes > 2, "Number of y nodes must be greater than 2"
15+
16+
self.__time = time
17+
self.__t_nodes = t_nodes
18+
self.__k = k
19+
self.__xlength = xlength
20+
self.__x_nodes = x_nodes
21+
self.__ylength = ylength if ylength is not None else xlength
22+
self.__y_nodes = y_nodes if y_nodes is not None else x_nodes
23+
# Initialize boundary conditions to None
24+
self.__initial_temp = None
25+
self.__left_boundary = None
26+
self.__right_boundary = None
27+
self.__top_boundary = None
28+
self.__bottom_boundary = None
29+
30+
def set_initial_temp(self, u0):
31+
self.__initial_temp = u0
32+
33+
def set_left_boundary_temp(self, left):
34+
self.__left_boundary = left
35+
36+
def set_right_boundary_temp(self, right):
37+
self.__right_boundary = right
38+
39+
def set_top_boundary_temp(self, top):
40+
self.__top_boundary = top
41+
42+
def set_bottom_boundary_temp(self, bottom):
43+
self.__bottom_boundary = bottom
44+
45+
@property
46+
def xlength(self):
47+
return self.__xlength
48+
49+
@property
50+
def ylength(self):
51+
return self.__ylength
52+
53+
@property
54+
def x_nodes(self):
55+
return self.__x_nodes
56+
57+
@property
58+
def y_nodes(self):
59+
return self.__y_nodes
60+
61+
@property
62+
def time(self):
63+
return self.__time
64+
65+
@property
66+
def t_nodes(self):
67+
return self.__t_nodes
68+
69+
@property
70+
def k(self):
71+
return self.__k
72+
73+
@property
74+
def initial_temp(self):
75+
return self.__initial_temp
76+
77+
@property
78+
def left_boundary(self):
79+
return self.__left_boundary
80+
81+
@property
82+
def right_boundary(self):
83+
return self.__right_boundary
84+
85+
@property
86+
def top_boundary(self):
87+
return self.__top_boundary
88+
89+
@property
90+
def bottom_boundary(self):
91+
return self.__bottom_boundary

pdesolvers/solution/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .solution import Solution1D, SolutionBlackScholes
1+
from .solution import Solution1D, SolutionBlackScholes, Heat2DSolution

pdesolvers/solution/solution.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from matplotlib.animation import FuncAnimation
12
import numpy as np
23
import pdesolvers.utils.utility as utility
34
import pdesolvers.enums.enums as enum
@@ -165,4 +166,64 @@ def _set_plot_labels(self, ax):
165166
ax.set_xlabel('Time')
166167
ax.set_ylabel('Asset Price')
167168
ax.set_zlabel(f'{self.option_type.value} Option Value')
168-
ax.set_title(f'{self.option_type.value} Option Value Surface Plot')
169+
ax.set_title(f'{self.option_type.value} Option Value Surface Plot')
170+
171+
class Heat2DSolution:
172+
def __init__(self, result, x_grid, y_grid, t_grid, dx, dy, dt, duration):
173+
self.result = result
174+
self.x_grid = x_grid
175+
self.y_grid = y_grid
176+
self.t_grid = t_grid
177+
self.dx = dx
178+
self.dy = dy
179+
self.dt = dt
180+
self.duration = duration
181+
182+
@staticmethod
183+
def __plot_surface(u_k, k, ax, xDomain, yDomain, dt):
184+
ax.clear()
185+
X, Y = np.meshgrid(xDomain, yDomain)
186+
# Transpose u_k to match meshgrid orientation
187+
surf = ax.plot_surface(X, Y, u_k.T,
188+
cmap='hot',
189+
alpha=0.9)
190+
ax.set_xlabel('X Position')
191+
ax.set_ylabel('Y Position')
192+
ax.set_zlabel('Temperature')
193+
ax.set_title(f'2D Heat Equation: t = {k*dt:.4f}')
194+
ax.view_init(elev=30, azim=45)
195+
ax.set_zlim(0, 100)
196+
197+
return surf
198+
199+
def animate(self, export=False, filename="heat_equation_2d_plot.gif"):
200+
print("Creating animation...")
201+
self
202+
fig = plt.figure(figsize=(12, 8))
203+
ax = fig.add_subplot(111, projection='3d')
204+
205+
def animateFrame(k):
206+
return Heat2DSolution.__plot_surface(self.result[k], k, ax, self.x_grid, self.y_grid, self.dt)
207+
208+
anim = FuncAnimation(fig, animateFrame, interval=100, frames=len(self.t_grid), repeat=True)
209+
210+
if export:
211+
anim.save(filename+'.gif', writer='pillow', fps=5)
212+
213+
plt.show()
214+
215+
def get_result(self):
216+
"""
217+
Gets the grid of computed temperature values
218+
219+
:return: grid result
220+
"""
221+
return self.result
222+
223+
def get_execution_time(self):
224+
"""
225+
Gets the time taken for the solver to solve the equation
226+
:return: duration
227+
"""
228+
return self.duration
229+

pdesolvers/solvers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .heat_solvers import Heat1DExplicitSolver, Heat1DCNSolver
2+
from .heat2d_solvers import Heat2DExplicitSolver, Heat2DCNSolver
23
from .black_scholes_solvers import BlackScholesExplicitSolver, BlackScholesCNSolver

0 commit comments

Comments
 (0)