pdesolver 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pdesolver/Auxs/FuncAux.py +24 -0
- pdesolver/Auxs/PDESEncoder.py +13 -0
- pdesolver/Auxs/Visualize.py +266 -0
- pdesolver/Auxs/__init__.py +2 -0
- pdesolver/Disc/Disc.py +372 -0
- pdesolver/Disc/__init__.py +1 -0
- pdesolver/Disc/boundaries/__init__.py +44 -0
- pdesolver/Disc/boundaries/boundary_base.py +36 -0
- pdesolver/Disc/boundaries/dirichlet.py +83 -0
- pdesolver/Disc/boundaries/neumann.py +48 -0
- pdesolver/Disc/boundaries/periodic_example.py +30 -0
- pdesolver/Disc/boundaries/robin.py +51 -0
- pdesolver/PDE.py +29 -0
- pdesolver/PDES.py +175 -0
- pdesolver/Solvers/CN.py +151 -0
- pdesolver/Solvers/RKF.py +285 -0
- pdesolver/Solvers/__init__.py +3 -0
- pdesolver/Solvers/bdf2.py +177 -0
- pdesolver/Solvers/solver_base.py +259 -0
- pdesolver/Solvers/solver_base2.py +159 -0
- pdesolver/__init__.py +5 -0
- pdesolver-0.1.0.dist-info/METADATA +99 -0
- pdesolver-0.1.0.dist-info/RECORD +25 -0
- pdesolver-0.1.0.dist-info/WHEEL +5 -0
- pdesolver-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import sympy as sp
|
|
4
|
+
from sympy.parsing.sympy_parser import parse_expr
|
|
5
|
+
|
|
6
|
+
def symbol_references(in_list):
|
|
7
|
+
slist = []
|
|
8
|
+
|
|
9
|
+
for e in in_list:
|
|
10
|
+
globals()[e] = sp.Symbol(e)
|
|
11
|
+
slist.append(e)
|
|
12
|
+
return slist
|
|
13
|
+
|
|
14
|
+
def d_dt(expr_str: str) -> str:
|
|
15
|
+
t = sp.Symbol('t')
|
|
16
|
+
try:
|
|
17
|
+
e = parse_expr(expr_str, evaluate=False)
|
|
18
|
+
return str(sp.diff(e, t))
|
|
19
|
+
except Exception:
|
|
20
|
+
return expr_str
|
|
21
|
+
|
|
22
|
+
def repl_symbol(expr: str, sym: str, repl: str) -> str:
|
|
23
|
+
pattern = rf'(?<![A-Za-z0-9_]){sym}(?![A-Za-z0-9_])'
|
|
24
|
+
return re.sub(pattern, repl, expr)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import numpy as np
|
|
3
|
+
import sympy as sp
|
|
4
|
+
|
|
5
|
+
class PDESEncoder(json.JSONEncoder):
|
|
6
|
+
def default(self, obj):
|
|
7
|
+
if isinstance(obj, np.ndarray):
|
|
8
|
+
return obj.tolist()
|
|
9
|
+
if isinstance(obj, sp.Basic):
|
|
10
|
+
return str(obj)
|
|
11
|
+
if hasattr(obj, '__dict__'):
|
|
12
|
+
return obj.__dict__
|
|
13
|
+
return super().default(obj)
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from matplotlib.animation import FuncAnimation
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def visualize(pdes_obj, mode='heatmap', func_idx=0, time_step=-1, **kwargs):
|
|
8
|
+
|
|
9
|
+
if pdes_obj.results is None:
|
|
10
|
+
print("Erro: rode .solve() antes de visualizar.")
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
_, historico = pdes_obj.results
|
|
14
|
+
|
|
15
|
+
if not historico or not historico[func_idx]:
|
|
16
|
+
print("Erro: histórico vazio.")
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
if _is_1d(pdes_obj):
|
|
20
|
+
x = _get_x1d(pdes_obj)
|
|
21
|
+
if mode in ('plot1d', 'plot'):
|
|
22
|
+
plot1d(historico, x, pdes_obj.funcs, func_idx, time_step, **kwargs)
|
|
23
|
+
elif mode == 'plot1d_all':
|
|
24
|
+
plot1d_all(historico, x, pdes_obj.funcs, func_idx, **kwargs)
|
|
25
|
+
elif mode == 'heatmap1d':
|
|
26
|
+
heatmap1d(historico, x, pdes_obj.funcs, pdes_obj.pdes, func_idx, **kwargs)
|
|
27
|
+
elif mode == 'animation1d':
|
|
28
|
+
animate1d(historico, x, pdes_obj.funcs, func_idx, **kwargs)
|
|
29
|
+
else:
|
|
30
|
+
print(f"Modo '{mode}' não reconhecido para 1D. "
|
|
31
|
+
f"Use: 'plot1d', 'plot1d_all', 'heatmap1d', 'animation1d'.")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
nx, ny = pdes_obj.disc_n
|
|
35
|
+
x = np.linspace(0, 1, nx)
|
|
36
|
+
y = np.linspace(0, 1, ny)
|
|
37
|
+
X, Y = np.meshgrid(x, y, indexing='ij')
|
|
38
|
+
|
|
39
|
+
if mode == 'heatmap':
|
|
40
|
+
plot_heatmap(historico, X, Y, pdes_obj.funcs, pdes_obj.disc_n, func_idx, time_step)
|
|
41
|
+
elif mode == 'animation':
|
|
42
|
+
animate2d(historico, X, Y, pdes_obj.funcs, pdes_obj.disc_n, func_idx, **kwargs)
|
|
43
|
+
elif mode == 'plot3d':
|
|
44
|
+
plot3d(historico, X, Y, pdes_obj.funcs, pdes_obj.disc_n, func_idx, time_step, **kwargs)
|
|
45
|
+
elif mode == 'animation3d':
|
|
46
|
+
animate3d(historico, X, Y, pdes_obj.funcs, pdes_obj.disc_n, func_idx, **kwargs)
|
|
47
|
+
else:
|
|
48
|
+
print(f"Modo '{mode}' desconhecido para 2D. "
|
|
49
|
+
f"Use: 'heatmap', 'animation', 'plot3d', 'animation3d'.")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _is_1d(pdes_obj):
|
|
53
|
+
return len(pdes_obj.disc_n) == 1
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _get_x1d(pdes_obj):
|
|
57
|
+
a, b = pdes_obj.pdes[0].ivar_boundary[0]
|
|
58
|
+
return np.linspace(a, b, pdes_obj.disc_n[0])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def plot1d(historico, x, funcs, func_idx, time_step, **kwargs):
|
|
62
|
+
color = kwargs.get('color', 'steelblue')
|
|
63
|
+
lw = kwargs.get('lw', 2)
|
|
64
|
+
|
|
65
|
+
data = np.array(historico[func_idx][time_step])
|
|
66
|
+
label = time_step if time_step >= 0 else len(historico[func_idx]) + time_step
|
|
67
|
+
|
|
68
|
+
fig, ax = plt.subplots(figsize=(7, 4))
|
|
69
|
+
ax.plot(x, data, color=color, linewidth=lw, label=f't = passo {label}')
|
|
70
|
+
ax.set_xlabel('x')
|
|
71
|
+
ax.set_ylabel(funcs[func_idx])
|
|
72
|
+
ax.set_title(f"Perfil de {funcs[func_idx]} — passo {label}")
|
|
73
|
+
ax.legend()
|
|
74
|
+
ax.grid(True, alpha=0.3)
|
|
75
|
+
plt.tight_layout()
|
|
76
|
+
plt.show()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def plot1d_all(historico, x, funcs, func_idx, **kwargs):
|
|
80
|
+
n_profiles = kwargs.get('n_profiles', 10)
|
|
81
|
+
cmap = kwargs.get('cmap', 'viridis')
|
|
82
|
+
lw = kwargs.get('lw', 1.5)
|
|
83
|
+
tf_real = kwargs.get('tf', None)
|
|
84
|
+
|
|
85
|
+
n_passos = len(historico[func_idx])
|
|
86
|
+
indices = np.linspace(0, n_passos - 1, n_profiles, dtype=int)
|
|
87
|
+
cm = plt.get_cmap(cmap)
|
|
88
|
+
colors = [cm(i / (n_profiles - 1)) for i in range(n_profiles)]
|
|
89
|
+
|
|
90
|
+
fig, ax = plt.subplots(figsize=(8, 5))
|
|
91
|
+
|
|
92
|
+
for idx_cor, idx_passo in enumerate(indices):
|
|
93
|
+
data = np.array(historico[func_idx][idx_passo])
|
|
94
|
+
if tf_real is not None:
|
|
95
|
+
t_val = tf_real * idx_passo / (n_passos - 1)
|
|
96
|
+
lbl = f't = {t_val:.2f}'
|
|
97
|
+
else:
|
|
98
|
+
lbl = f'passo {idx_passo}'
|
|
99
|
+
ax.plot(x, data, color=colors[idx_cor], linewidth=lw, label=lbl)
|
|
100
|
+
|
|
101
|
+
ax.set_xlabel('x')
|
|
102
|
+
ax.set_ylabel(funcs[func_idx])
|
|
103
|
+
ax.set_title(f"Evolução de {funcs[func_idx]} ao longo do tempo")
|
|
104
|
+
ax.legend(fontsize=8, loc='best')
|
|
105
|
+
ax.grid(True, alpha=0.3)
|
|
106
|
+
|
|
107
|
+
sm = plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(vmin=0, vmax=1))
|
|
108
|
+
sm.set_array([])
|
|
109
|
+
cbar = plt.colorbar(sm, ax=ax, pad=0.02)
|
|
110
|
+
cbar.set_label('t / tf')
|
|
111
|
+
|
|
112
|
+
plt.tight_layout()
|
|
113
|
+
plt.show()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def heatmap1d(historico, x, funcs, pdes, func_idx, **kwargs):
|
|
117
|
+
cmap = kwargs.get('cmap', 'viridis')
|
|
118
|
+
tf_real = kwargs.get('tf', None)
|
|
119
|
+
|
|
120
|
+
matriz = np.array(historico[func_idx])
|
|
121
|
+
n_passos = matriz.shape[0]
|
|
122
|
+
t_max = tf_real if tf_real is not None else n_passos - 1
|
|
123
|
+
a, b = pdes[0].ivar_boundary[0]
|
|
124
|
+
|
|
125
|
+
fig, ax = plt.subplots(figsize=(8, 5))
|
|
126
|
+
im = ax.imshow(
|
|
127
|
+
matriz.T,
|
|
128
|
+
aspect='auto',
|
|
129
|
+
origin='lower',
|
|
130
|
+
extent=[0, t_max, a, b],
|
|
131
|
+
cmap=cmap
|
|
132
|
+
)
|
|
133
|
+
ax.set_xlabel('t')
|
|
134
|
+
ax.set_ylabel('x')
|
|
135
|
+
ax.set_title(f"Evolução espaciotemporal de {funcs[func_idx]}")
|
|
136
|
+
plt.colorbar(im, ax=ax, label=funcs[func_idx])
|
|
137
|
+
plt.tight_layout()
|
|
138
|
+
plt.show()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def animate1d(historico, x, funcs, func_idx, **kwargs):
|
|
142
|
+
frames_step = kwargs.get('frames_step', 1)
|
|
143
|
+
interval = kwargs.get('interval', 50)
|
|
144
|
+
color = kwargs.get('color', 'steelblue')
|
|
145
|
+
lw = kwargs.get('lw', 2)
|
|
146
|
+
tf_real = kwargs.get('tf', None)
|
|
147
|
+
|
|
148
|
+
n_passos = len(historico[func_idx])
|
|
149
|
+
frames = list(range(0, n_passos, frames_step))
|
|
150
|
+
|
|
151
|
+
todos = np.concatenate([historico[func_idx][f] for f in frames])
|
|
152
|
+
y_min, y_max = todos.min(), todos.max()
|
|
153
|
+
margem = (y_max - y_min) * 0.05 or 0.1
|
|
154
|
+
|
|
155
|
+
fig, ax = plt.subplots(figsize=(8, 4))
|
|
156
|
+
linha, = ax.plot(x, historico[func_idx][0], color=color, linewidth=lw)
|
|
157
|
+
ax.set_xlim(x[0], x[-1])
|
|
158
|
+
ax.set_ylim(y_min - margem, y_max + margem)
|
|
159
|
+
ax.set_xlabel('x')
|
|
160
|
+
ax.set_ylabel(funcs[func_idx])
|
|
161
|
+
ax.grid(True, alpha=0.3)
|
|
162
|
+
titulo = ax.set_title('')
|
|
163
|
+
|
|
164
|
+
def update(frame_idx):
|
|
165
|
+
passo = frames[frame_idx]
|
|
166
|
+
linha.set_ydata(np.array(historico[func_idx][passo]))
|
|
167
|
+
if tf_real is not None:
|
|
168
|
+
t_val = tf_real * passo / (n_passos - 1)
|
|
169
|
+
titulo.set_text(f"{funcs[func_idx]} — t = {t_val:.3f}")
|
|
170
|
+
else:
|
|
171
|
+
titulo.set_text(f"{funcs[func_idx]} — passo {passo}")
|
|
172
|
+
return linha, titulo
|
|
173
|
+
|
|
174
|
+
anim = FuncAnimation(fig, update, frames=len(frames),
|
|
175
|
+
interval=interval, blit=True)
|
|
176
|
+
plt.tight_layout()
|
|
177
|
+
plt.show()
|
|
178
|
+
|
|
179
|
+
def plot_heatmap(historico, X, Y, funcs, disc_n, func_idx, time_step):
|
|
180
|
+
data = np.array(historico[func_idx][time_step]).reshape(disc_n)
|
|
181
|
+
plt.figure(figsize=(7, 6))
|
|
182
|
+
contorno = plt.contourf(X, Y, data, levels=30, cmap='hot')
|
|
183
|
+
plt.title(f"Distribuição de {funcs[func_idx]} no passo {time_step}")
|
|
184
|
+
plt.colorbar(contorno)
|
|
185
|
+
plt.show()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def animate2d(historico, X, Y, funcs, disc_n, func_idx, **kwargs):
|
|
189
|
+
frames_step = kwargs.get('frames_step', 1)
|
|
190
|
+
interval = kwargs.get('interval', 50)
|
|
191
|
+
|
|
192
|
+
fig, ax = plt.subplots(figsize=(7, 6))
|
|
193
|
+
Z_ini = np.array(historico[func_idx][0]).reshape(disc_n)
|
|
194
|
+
contorno = ax.contourf(X, Y, Z_ini, levels=30, cmap='hot')
|
|
195
|
+
fig.colorbar(contorno, ax=ax)
|
|
196
|
+
|
|
197
|
+
def update(frame):
|
|
198
|
+
ax.clear()
|
|
199
|
+
Z = np.array(historico[func_idx][frame]).reshape(disc_n)
|
|
200
|
+
cont = ax.contourf(X, Y, Z, levels=30, cmap='hot')
|
|
201
|
+
ax.set_title(f"Evolução de {funcs[func_idx]} - Passo {frame}")
|
|
202
|
+
ax.set_xlabel('x')
|
|
203
|
+
ax.set_ylabel('y')
|
|
204
|
+
return cont,
|
|
205
|
+
|
|
206
|
+
frames_list = range(0, len(historico[func_idx]), frames_step)
|
|
207
|
+
anim = FuncAnimation(fig, update, frames=frames_list,
|
|
208
|
+
interval=interval, blit=False)
|
|
209
|
+
plt.show()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def plot3d(historico, X, Y, funcs, disc_n, func_idx, time_step, **kwargs):
|
|
213
|
+
cmap = kwargs.get('cmap', 'hot')
|
|
214
|
+
alpha = kwargs.get('alpha', 1.0)
|
|
215
|
+
elev = kwargs.get('elev', 30)
|
|
216
|
+
azim = kwargs.get('azim', -60)
|
|
217
|
+
|
|
218
|
+
data = np.array(historico[func_idx][time_step]).reshape(disc_n)
|
|
219
|
+
label = time_step if time_step >= 0 else len(historico[func_idx]) + time_step
|
|
220
|
+
|
|
221
|
+
fig = plt.figure(figsize=(8, 6))
|
|
222
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
223
|
+
ax.plot_surface(X, Y, data, cmap=cmap, alpha=alpha)
|
|
224
|
+
ax.set_title(f"Superfície 3D de {funcs[func_idx]} - Passo {label}")
|
|
225
|
+
ax.set_xlabel('x')
|
|
226
|
+
ax.set_ylabel('y')
|
|
227
|
+
ax.set_zlabel(str(funcs[func_idx]))
|
|
228
|
+
ax.view_init(elev=elev, azim=azim)
|
|
229
|
+
plt.tight_layout()
|
|
230
|
+
plt.show()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def animate3d(historico, X, Y, funcs, disc_n, func_idx, **kwargs):
|
|
234
|
+
frames_step = kwargs.get('frames_step', 1)
|
|
235
|
+
interval = kwargs.get('interval', 50)
|
|
236
|
+
cmap = kwargs.get('cmap', 'hot')
|
|
237
|
+
alpha = kwargs.get('alpha', 1.0)
|
|
238
|
+
elev = kwargs.get('elev', 30)
|
|
239
|
+
azim = kwargs.get('azim', -60)
|
|
240
|
+
|
|
241
|
+
todos_frames = [
|
|
242
|
+
np.array(historico[func_idx][f]).reshape(disc_n)
|
|
243
|
+
for f in range(0, len(historico[func_idx]), frames_step)
|
|
244
|
+
]
|
|
245
|
+
z_min = min(Z.min() for Z in todos_frames)
|
|
246
|
+
z_max = max(Z.max() for Z in todos_frames)
|
|
247
|
+
|
|
248
|
+
fig = plt.figure(figsize=(8, 6))
|
|
249
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
250
|
+
|
|
251
|
+
def update(frame):
|
|
252
|
+
ax.clear()
|
|
253
|
+
Z = todos_frames[frame]
|
|
254
|
+
ax.plot_surface(X, Y, Z, cmap=cmap, alpha=alpha)
|
|
255
|
+
ax.set_title(
|
|
256
|
+
f"Evolução 3D de {funcs[func_idx]} - Passo {frame * frames_step}"
|
|
257
|
+
)
|
|
258
|
+
ax.set_xlabel('x')
|
|
259
|
+
ax.set_ylabel('y')
|
|
260
|
+
ax.set_zlabel(str(funcs[func_idx]))
|
|
261
|
+
ax.set_zlim(z_min, z_max)
|
|
262
|
+
ax.view_init(elev=elev, azim=azim)
|
|
263
|
+
|
|
264
|
+
anim = FuncAnimation(fig, update, frames=len(todos_frames),
|
|
265
|
+
interval=interval, blit=False)
|
|
266
|
+
plt.show()
|
pdesolver/Disc/Disc.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
import re
|
|
3
|
+
from ..Auxs.FuncAux import repl_symbol as _repl_symbol
|
|
4
|
+
from .boundaries import get_boundary
|
|
5
|
+
|
|
6
|
+
def _build_discretized_eqs(
|
|
7
|
+
eqrs: List[str],
|
|
8
|
+
xd_var: List[str],
|
|
9
|
+
str_sp_vars: str,
|
|
10
|
+
method: str,
|
|
11
|
+
) -> List[str]:
|
|
12
|
+
|
|
13
|
+
SCHEMES = {
|
|
14
|
+
"forward": {
|
|
15
|
+
"d2x": "({v}_i+1_j - 2*{v}_ii_j + {v}_i-1_j)/ h{v}_ ** 2",
|
|
16
|
+
"d1x": "({v}_i+1_j - {v}_ii_j)/ h{v}_",
|
|
17
|
+
"d2y": "({v}_ii_j+1 - 2*{v}_ii_j + {v}_ii_j-1)/ h{v}_ ** 2",
|
|
18
|
+
"d1y": "({v}_ii_j+1 - {v}_ii_j)/ h{v}_",
|
|
19
|
+
},
|
|
20
|
+
"central": {
|
|
21
|
+
"d2x": "({v}_i+1_j - 2*{v}_ii_j + {v}_i-1_j)/ h{v}_ ** 2",
|
|
22
|
+
"d1x": "({v}_i+1_j - {v}_i-1_j)/(2* h{v}_)",
|
|
23
|
+
"d2y": "({v}_ii_j+1 - 2*{v}_ii_j + {v}_ii_j-1)/ h{v}_ ** 2",
|
|
24
|
+
"d1y": "({v}_ii_j+1 - {v}_ii_j-1)/(2* h{v}_)",
|
|
25
|
+
},
|
|
26
|
+
"backward": {
|
|
27
|
+
"d2x": "({v}_i+1_j - 2*{v}_ii_j + {v}_i-1_j)/ h{v}_ ** 2",
|
|
28
|
+
"d1x": "({v}_ii_j - {v}_i-1_j)/ h{v}_",
|
|
29
|
+
"d2y": "({v}_ii_j+1 - 2*{v}_ii_j + {v}_ii_j-1)/ h{v}_ ** 2",
|
|
30
|
+
"d1y": "({v}_ii_j - {v}_ii_j-1)/ h{v}_",
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if method not in SCHEMES:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Método inválido: '{method}'. Use 'forward', 'central' ou 'backward'."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
s = SCHEMES[method]
|
|
40
|
+
|
|
41
|
+
for j in range(len(eqrs)):
|
|
42
|
+
for k, sp_var in enumerate(str_sp_vars):
|
|
43
|
+
for v in xd_var:
|
|
44
|
+
if k == 0:
|
|
45
|
+
eqrs[j] = eqrs[j].replace(f"d2{v}{str_sp_vars}/d{sp_var}2", s["d2x"].format(v=v))
|
|
46
|
+
eqrs[j] = eqrs[j].replace(f"d{v}{str_sp_vars}/d{sp_var}", s["d1x"].format(v=v))
|
|
47
|
+
elif k == 1:
|
|
48
|
+
eqrs[j] = eqrs[j].replace(f"d2{v}{str_sp_vars}/d{sp_var}2", s["d2y"].format(v=v))
|
|
49
|
+
eqrs[j] = eqrs[j].replace(f"d{v}{str_sp_vars}/d{sp_var}", s["d1y"].format(v=v))
|
|
50
|
+
|
|
51
|
+
for v in xd_var:
|
|
52
|
+
eqrs[j] = eqrs[j].replace(f"{v}{str_sp_vars}", f"{v}_ii_j")
|
|
53
|
+
|
|
54
|
+
for j in range(len(eqrs)):
|
|
55
|
+
eqrs[j] = _repl_symbol(eqrs[j], str_sp_vars[0], f"ii * h{xd_var[0]}_")
|
|
56
|
+
if len(str_sp_vars) == 2:
|
|
57
|
+
eqrs[j] = _repl_symbol(eqrs[j], str_sp_vars[1], f"j * h{xd_var[0]}_")
|
|
58
|
+
|
|
59
|
+
return eqrs
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _expand_indices(
|
|
63
|
+
eqrs: List[str],
|
|
64
|
+
n_part: List[int],
|
|
65
|
+
str_sp_vars: str,
|
|
66
|
+
) -> List[List[str]]:
|
|
67
|
+
|
|
68
|
+
partial = []
|
|
69
|
+
for eq in eqrs:
|
|
70
|
+
row = [
|
|
71
|
+
eq
|
|
72
|
+
.replace("i+1", str(i + 1))
|
|
73
|
+
.replace("i-1", str(i - 1))
|
|
74
|
+
.replace("i-2", str(i - 2))
|
|
75
|
+
.replace("i+2", str(i + 2))
|
|
76
|
+
.replace("ii", str(i))
|
|
77
|
+
for i in range(1, n_part[0] - 1)
|
|
78
|
+
]
|
|
79
|
+
partial.append(row)
|
|
80
|
+
|
|
81
|
+
list_eq: List[List[str]] = [[] for _ in partial]
|
|
82
|
+
|
|
83
|
+
if len(str_sp_vars) == 2:
|
|
84
|
+
for j_eq, row in enumerate(partial):
|
|
85
|
+
for eq_i in row:
|
|
86
|
+
for k in range(1, n_part[1] - 1):
|
|
87
|
+
list_eq[j_eq].append(
|
|
88
|
+
eq_i
|
|
89
|
+
.replace("j+1", str(k + 1))
|
|
90
|
+
.replace("j-1", str(k - 1))
|
|
91
|
+
.replace("j-2", str(k - 2))
|
|
92
|
+
.replace("j+2", str(k + 2))
|
|
93
|
+
.replace("j", str(k))
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
for j_eq, row in enumerate(partial):
|
|
97
|
+
for eq_i in row:
|
|
98
|
+
list_eq[j_eq].append(eq_i.replace("j", "0"))
|
|
99
|
+
|
|
100
|
+
return list_eq
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _build_position_labels(n_part, str_sp_vars, n_funcs):
|
|
104
|
+
positions = []
|
|
105
|
+
|
|
106
|
+
if len(str_sp_vars) == 2:
|
|
107
|
+
for func in range(n_funcs):
|
|
108
|
+
aux = []
|
|
109
|
+
for i in range(n_part[0]):
|
|
110
|
+
for j in range(n_part[1]):
|
|
111
|
+
if i == 0: aux.append(f"W{func}_{i}_{j}")
|
|
112
|
+
elif i == n_part[0] - 1: aux.append(f"E{func}_{i}_{j}")
|
|
113
|
+
elif j == 0: aux.append(f"S{func}_{i}_{j}")
|
|
114
|
+
elif j == n_part[1] - 1: aux.append(f"N{func}_{i}_{j}")
|
|
115
|
+
else: aux.append(f"Ce{func}_{i}_{j}")
|
|
116
|
+
positions.append(aux)
|
|
117
|
+
else:
|
|
118
|
+
positions = [[""] * n_part[0] for _ in range(n_funcs)]
|
|
119
|
+
|
|
120
|
+
return positions
|
|
121
|
+
|
|
122
|
+
def df(
|
|
123
|
+
pdes,
|
|
124
|
+
n_part: List[int],
|
|
125
|
+
west_bd=None,
|
|
126
|
+
method: str = "forward",
|
|
127
|
+
north_bd=None,
|
|
128
|
+
south_bd=None,
|
|
129
|
+
east_bd=None,
|
|
130
|
+
north_func_bd=None,
|
|
131
|
+
south_func_bd=None,
|
|
132
|
+
west_func_bd=None,
|
|
133
|
+
east_func_bd=None,
|
|
134
|
+
north_alpha_bd: str = "0",
|
|
135
|
+
south_alpha_bd: str = "0",
|
|
136
|
+
east_alpha_bd: str = "0",
|
|
137
|
+
north_beta_bd: str = "1",
|
|
138
|
+
south_beta_bd: str = "1",
|
|
139
|
+
east_beta_bd: str = "1",
|
|
140
|
+
) -> Tuple[List[str], List[str], dict, dict]:
|
|
141
|
+
|
|
142
|
+
n_funcs_total = len(pdes.funcs)
|
|
143
|
+
|
|
144
|
+
def _as_list(val, default="neumann"):
|
|
145
|
+
if val is None:
|
|
146
|
+
return [default] * n_funcs_total
|
|
147
|
+
if isinstance(val, list):
|
|
148
|
+
return val
|
|
149
|
+
return [val] * n_funcs_total
|
|
150
|
+
|
|
151
|
+
west_bd = _as_list(west_bd, "neumann")
|
|
152
|
+
east_bd = _as_list(east_bd, "neumann")
|
|
153
|
+
north_bd = _as_list(north_bd, "neumann")
|
|
154
|
+
south_bd = _as_list(south_bd, "neumann")
|
|
155
|
+
west_func_bd = _as_list(west_func_bd, "0")
|
|
156
|
+
east_func_bd = _as_list(east_func_bd, "0")
|
|
157
|
+
north_func_bd = _as_list(north_func_bd, "0")
|
|
158
|
+
south_func_bd = _as_list(south_func_bd, "0")
|
|
159
|
+
|
|
160
|
+
xd_var = pdes.xs(pdes.funcs)
|
|
161
|
+
eqrs = [eq.split("=")[1] for eq in pdes.eqs]
|
|
162
|
+
str_sp_vars = "".join(pdes.sp_vars)
|
|
163
|
+
|
|
164
|
+
for j in range(len(eqrs)):
|
|
165
|
+
for i, func in enumerate(pdes.funcs):
|
|
166
|
+
eqrs[j] = eqrs[j].replace(str(func), f"{xd_var[i]}{str_sp_vars}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
eqrs = _build_discretized_eqs(eqrs, xd_var, str_sp_vars, method)
|
|
170
|
+
|
|
171
|
+
list_eq = _expand_indices(eqrs, n_part, str_sp_vars)
|
|
172
|
+
|
|
173
|
+
n_funcs = len(pdes.funcs)
|
|
174
|
+
|
|
175
|
+
if len(str_sp_vars) == 2:
|
|
176
|
+
list_south = [[] for _ in range(n_funcs)]
|
|
177
|
+
list_north = [[] for _ in range(n_funcs)]
|
|
178
|
+
list_west = [[] for _ in range(n_funcs)]
|
|
179
|
+
list_east = [[] for _ in range(n_funcs)]
|
|
180
|
+
|
|
181
|
+
for func in range(n_funcs):
|
|
182
|
+
s_bc = get_boundary(south_bd[func], south_func_bd[func], south_alpha_bd, south_beta_bd)
|
|
183
|
+
list_south[func] = s_bc.apply("south", list_eq, n_part, xd_var, str_sp_vars)[func]
|
|
184
|
+
|
|
185
|
+
n_bc = get_boundary(north_bd[func], north_func_bd[func], north_alpha_bd, north_beta_bd)
|
|
186
|
+
list_north[func] = n_bc.apply("north", list_eq, n_part, xd_var, str_sp_vars)[func]
|
|
187
|
+
|
|
188
|
+
w_bc = get_boundary(west_bd[func], west_func_bd[func])
|
|
189
|
+
list_west[func] = w_bc.apply("west", list_eq, n_part, xd_var, str_sp_vars)[func] \
|
|
190
|
+
if west_bd[func].lower() in ("dirichlet", "neumann", "robin") else []
|
|
191
|
+
|
|
192
|
+
e_bc = get_boundary(east_bd[func], east_func_bd[func], east_alpha_bd, east_beta_bd)
|
|
193
|
+
east_col = e_bc.apply("east", list_eq, n_part, xd_var, str_sp_vars)[func]
|
|
194
|
+
list_east[func] = [list_south[func][-1]] + east_col + [list_north[func][-1]]
|
|
195
|
+
|
|
196
|
+
list_positions = _build_position_labels(n_part, str_sp_vars, n_funcs)
|
|
197
|
+
|
|
198
|
+
if len(str_sp_vars) == 1:
|
|
199
|
+
for func in range(n_funcs):
|
|
200
|
+
C = 0
|
|
201
|
+
for i in range(n_part[0]):
|
|
202
|
+
if i == 0:
|
|
203
|
+
bc = get_boundary(west_bd[func], west_func_bd[func])
|
|
204
|
+
list_positions[func][i] = bc.apply(
|
|
205
|
+
"west", list_eq, n_part, xd_var, str_sp_vars
|
|
206
|
+
)[func][0]
|
|
207
|
+
elif i == n_part[0] - 1:
|
|
208
|
+
bc = get_boundary(east_bd[func], east_func_bd[func], east_alpha_bd, east_beta_bd)
|
|
209
|
+
list_positions[func][i] = bc.apply(
|
|
210
|
+
"east", list_eq, n_part, xd_var, str_sp_vars
|
|
211
|
+
)[func][0]
|
|
212
|
+
else:
|
|
213
|
+
list_positions[func][i] = list_eq[func][C]
|
|
214
|
+
C += 1
|
|
215
|
+
|
|
216
|
+
elif len(str_sp_vars) == 2:
|
|
217
|
+
for func in range(n_funcs):
|
|
218
|
+
C = 0
|
|
219
|
+
for idx in range(len(list_positions[func])):
|
|
220
|
+
label = list_positions[func][idx]
|
|
221
|
+
if "S" in label: list_positions[func][idx] = list_south[func].pop(0) if list_south[func] else label
|
|
222
|
+
elif "N" in label: list_positions[func][idx] = list_north[func].pop(0) if list_north[func] else label
|
|
223
|
+
elif "E" in label: list_positions[func][idx] = list_east[func].pop(0) if list_east[func] else label
|
|
224
|
+
elif "W" in label: list_positions[func][idx] = list_west[func].pop(0) if list_west[func] else label
|
|
225
|
+
elif "C" in label:
|
|
226
|
+
list_positions[func][idx] = list_eq[func][C]
|
|
227
|
+
C += 1
|
|
228
|
+
|
|
229
|
+
d_vars: List[str] = []
|
|
230
|
+
if len(str_sp_vars) == 2:
|
|
231
|
+
for func in range(n_funcs):
|
|
232
|
+
for i in range(n_part[0]):
|
|
233
|
+
for j in range(n_part[1]):
|
|
234
|
+
name = f"XX{func}_{i}_{j}"
|
|
235
|
+
if name not in d_vars:
|
|
236
|
+
d_vars.append(name)
|
|
237
|
+
else:
|
|
238
|
+
for func in range(n_funcs):
|
|
239
|
+
for i in range(n_part[0]):
|
|
240
|
+
name = f"XX{func}_{i}_0"
|
|
241
|
+
if name not in d_vars:
|
|
242
|
+
d_vars.append(name)
|
|
243
|
+
|
|
244
|
+
flat_list_positions: List[str] = []
|
|
245
|
+
for L in list_positions:
|
|
246
|
+
flat_list_positions.extend(L)
|
|
247
|
+
|
|
248
|
+
hx_val = str(1.0 / (n_part[0] - 1))
|
|
249
|
+
for i in range(len(flat_list_positions)):
|
|
250
|
+
for func_name in xd_var:
|
|
251
|
+
flat_list_positions[i] = flat_list_positions[i].replace(
|
|
252
|
+
f"h{func_name}_", hx_val
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
_h_pattern = re.compile(r'\bh[A-Za-z0-9]+_\b')
|
|
256
|
+
for i in range(len(flat_list_positions)):
|
|
257
|
+
flat_list_positions[i] = _h_pattern.sub(hx_val, flat_list_positions[i])
|
|
258
|
+
|
|
259
|
+
dirichlet_constraints: dict = {}
|
|
260
|
+
neumann_constraints: dict = {}
|
|
261
|
+
hx = 1.0 / (n_part[0] - 1)
|
|
262
|
+
hy = 1.0 / (n_part[1] - 1) if len(str_sp_vars) == 2 else 1.0
|
|
263
|
+
|
|
264
|
+
if len(str_sp_vars) == 2:
|
|
265
|
+
sides = {
|
|
266
|
+
'west': west_bd,
|
|
267
|
+
'east': east_bd,
|
|
268
|
+
'north': north_bd,
|
|
269
|
+
'south': south_bd,
|
|
270
|
+
}
|
|
271
|
+
func_exprs = {
|
|
272
|
+
'west': west_func_bd,
|
|
273
|
+
'east': east_func_bd,
|
|
274
|
+
'north': north_func_bd,
|
|
275
|
+
'south': south_func_bd,
|
|
276
|
+
}
|
|
277
|
+
Nx, Ny = n_part[0], n_part[1]
|
|
278
|
+
|
|
279
|
+
any_neumann = any(
|
|
280
|
+
sides[s][f].lower() == 'neumann'
|
|
281
|
+
for s in sides for f in range(n_funcs)
|
|
282
|
+
)
|
|
283
|
+
if any_neumann and (Nx < 3 or Ny < 3):
|
|
284
|
+
raise ValueError(
|
|
285
|
+
f"Neumann com discretizacao one-sided de 2a ordem exige "
|
|
286
|
+
f"min(Nx, Ny) >= 3. Recebido: Nx={Nx}, Ny={Ny}."
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
def _idx(func, i, j):
|
|
290
|
+
return func * Nx * Ny + i * Ny + j
|
|
291
|
+
|
|
292
|
+
for func in range(n_funcs):
|
|
293
|
+
for i in range(Nx):
|
|
294
|
+
for j in range(Ny):
|
|
295
|
+
idx = _idx(func, i, j)
|
|
296
|
+
side = None
|
|
297
|
+
if i == 0: side = 'west'
|
|
298
|
+
elif i == Nx - 1: side = 'east'
|
|
299
|
+
elif j == 0: side = 'south'
|
|
300
|
+
elif j == Ny - 1: side = 'north'
|
|
301
|
+
if side is None:
|
|
302
|
+
continue
|
|
303
|
+
bc_kind = sides[side][func].lower()
|
|
304
|
+
|
|
305
|
+
if bc_kind == 'dirichlet':
|
|
306
|
+
dirichlet_constraints[idx] = {
|
|
307
|
+
'expr': func_exprs[side][func],
|
|
308
|
+
'x': i * hx,
|
|
309
|
+
'y': j * hy,
|
|
310
|
+
}
|
|
311
|
+
elif bc_kind == 'neumann':
|
|
312
|
+
if side == 'south':
|
|
313
|
+
n1 = _idx(func, i, 1)
|
|
314
|
+
n2 = _idx(func, i, 2)
|
|
315
|
+
elif side == 'north':
|
|
316
|
+
n1 = _idx(func, i, Ny - 2)
|
|
317
|
+
n2 = _idx(func, i, Ny - 3)
|
|
318
|
+
elif side == 'west':
|
|
319
|
+
n1 = _idx(func, 1, j)
|
|
320
|
+
n2 = _idx(func, 2, j)
|
|
321
|
+
else:
|
|
322
|
+
n1 = _idx(func, Nx - 2, j)
|
|
323
|
+
n2 = _idx(func, Nx - 3, j)
|
|
324
|
+
|
|
325
|
+
neumann_constraints[idx] = {
|
|
326
|
+
'expr': func_exprs[side][func],
|
|
327
|
+
'x': i * hx,
|
|
328
|
+
'y': j * hy,
|
|
329
|
+
'side': side,
|
|
330
|
+
'idx_n1': n1,
|
|
331
|
+
'idx_n2': n2,
|
|
332
|
+
}
|
|
333
|
+
else:
|
|
334
|
+
Nx = n_part[0]
|
|
335
|
+
for func in range(n_funcs):
|
|
336
|
+
offset = func * Nx
|
|
337
|
+
if west_bd[func].lower() == 'dirichlet':
|
|
338
|
+
dirichlet_constraints[offset] = {
|
|
339
|
+
'expr': west_func_bd[func],
|
|
340
|
+
'x': 0.0,
|
|
341
|
+
'y': 0.0,
|
|
342
|
+
}
|
|
343
|
+
elif west_bd[func].lower() == 'neumann':
|
|
344
|
+
if Nx < 3:
|
|
345
|
+
raise ValueError("Neumann 1D exige Nx >= 3.")
|
|
346
|
+
neumann_constraints[offset] = {
|
|
347
|
+
'expr': west_func_bd[func],
|
|
348
|
+
'x': 0.0,
|
|
349
|
+
'y': 0.0,
|
|
350
|
+
'side': 'west',
|
|
351
|
+
'idx_n1': offset + 1,
|
|
352
|
+
'idx_n2': offset + 2,
|
|
353
|
+
}
|
|
354
|
+
if east_bd[func].lower() == 'dirichlet':
|
|
355
|
+
dirichlet_constraints[offset + Nx - 1] = {
|
|
356
|
+
'expr': east_func_bd[func],
|
|
357
|
+
'x': (Nx - 1) * hx,
|
|
358
|
+
'y': 0.0,
|
|
359
|
+
}
|
|
360
|
+
elif east_bd[func].lower() == 'neumann':
|
|
361
|
+
if Nx < 3:
|
|
362
|
+
raise ValueError("Neumann 1D exige Nx >= 3.")
|
|
363
|
+
neumann_constraints[offset + Nx - 1] = {
|
|
364
|
+
'expr': east_func_bd[func],
|
|
365
|
+
'x': (Nx - 1) * hx,
|
|
366
|
+
'y': 0.0,
|
|
367
|
+
'side': 'east',
|
|
368
|
+
'idx_n1': offset + Nx - 2,
|
|
369
|
+
'idx_n2': offset + Nx - 3,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return flat_list_positions, d_vars, dirichlet_constraints, neumann_constraints
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .Disc import df
|