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,44 @@
|
|
|
1
|
+
from .boundary_base import BoundaryCondition
|
|
2
|
+
from .dirichlet import DirichletBC
|
|
3
|
+
from .neumann import NeumannBC
|
|
4
|
+
from .robin import RobinBC
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
BOUNDARY_REGISTRY: dict[str, type[BoundaryCondition]] = {
|
|
8
|
+
"dirichlet": DirichletBC,
|
|
9
|
+
"neumann": NeumannBC,
|
|
10
|
+
"robin": RobinBC,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_boundary(
|
|
15
|
+
name: str,
|
|
16
|
+
bd_func: str,
|
|
17
|
+
alpha: str = "0",
|
|
18
|
+
beta: str = "1",
|
|
19
|
+
use_time_derivative: bool = True,
|
|
20
|
+
) -> BoundaryCondition:
|
|
21
|
+
|
|
22
|
+
key = name.lower()
|
|
23
|
+
if key not in BOUNDARY_REGISTRY:
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"Tipo de contorno desconhecido: '{name}'. "
|
|
26
|
+
f"Disponíveis: {list(BOUNDARY_REGISTRY.keys())}"
|
|
27
|
+
)
|
|
28
|
+
cls = BOUNDARY_REGISTRY[key]
|
|
29
|
+
|
|
30
|
+
if key == "dirichlet":
|
|
31
|
+
return cls(bd_func, use_time_derivative=use_time_derivative)
|
|
32
|
+
if key == "robin":
|
|
33
|
+
return cls(bd_func, alpha=alpha, beta=beta)
|
|
34
|
+
return cls(bd_func)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"BoundaryCondition",
|
|
39
|
+
"DirichletBC",
|
|
40
|
+
"NeumannBC",
|
|
41
|
+
"RobinBC",
|
|
42
|
+
"BOUNDARY_REGISTRY",
|
|
43
|
+
"get_boundary",
|
|
44
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BoundaryCondition(ABC):
|
|
6
|
+
|
|
7
|
+
def __init__(self, bd_func: str):
|
|
8
|
+
self.bd_func = bd_func
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def apply(
|
|
12
|
+
self,
|
|
13
|
+
bd: str,
|
|
14
|
+
list_eq: List[List[str]],
|
|
15
|
+
n_part: List[int],
|
|
16
|
+
xd_var: List[str],
|
|
17
|
+
str_sp_vars: str = "",
|
|
18
|
+
) -> List[List[str]]:
|
|
19
|
+
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def _valid_sides_2d() -> List[str]:
|
|
24
|
+
return ["north", "south", "east", "west"]
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def _valid_sides_1d() -> List[str]:
|
|
28
|
+
return ["east", "west"]
|
|
29
|
+
|
|
30
|
+
def _check_side(self, bd: str, is_2d: bool) -> None:
|
|
31
|
+
valid = self._valid_sides_2d() if is_2d else self._valid_sides_1d()
|
|
32
|
+
if bd.lower() not in valid:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"Contorno inválido: '{bd}'. Válidos para "
|
|
35
|
+
f"{'2D' if is_2d else '1D'}: {valid}"
|
|
36
|
+
)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import List
|
|
3
|
+
from ...Auxs.FuncAux import repl_symbol as _repl_symbol
|
|
4
|
+
from .boundary_base import BoundaryCondition
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DirichletBC(BoundaryCondition):
|
|
8
|
+
|
|
9
|
+
def __init__(self, bd_func: str, use_time_derivative: bool = True):
|
|
10
|
+
super().__init__(bd_func)
|
|
11
|
+
self.use_time_derivative = use_time_derivative
|
|
12
|
+
|
|
13
|
+
def _replace_xy(self, expr: str, X: str, Y: str, str_sp_vars: str) -> str:
|
|
14
|
+
out = _repl_symbol(expr, str_sp_vars[0], X)
|
|
15
|
+
if len(str_sp_vars) == 2:
|
|
16
|
+
out = _repl_symbol(out, str_sp_vars[1], Y)
|
|
17
|
+
return out
|
|
18
|
+
|
|
19
|
+
def apply(
|
|
20
|
+
self,
|
|
21
|
+
bd: str,
|
|
22
|
+
list_eq: List[List[str]],
|
|
23
|
+
n_part: List[int],
|
|
24
|
+
xd_var: List[str],
|
|
25
|
+
str_sp_vars: str = "",
|
|
26
|
+
) -> List[List[str]]:
|
|
27
|
+
|
|
28
|
+
is_2d = len(str_sp_vars) == 2
|
|
29
|
+
self._check_side(bd, is_2d)
|
|
30
|
+
bd = bd.lower()
|
|
31
|
+
|
|
32
|
+
if is_2d:
|
|
33
|
+
return self._apply_2d(bd, list_eq, n_part, xd_var, str_sp_vars)
|
|
34
|
+
return self._apply_1d(bd, list_eq, n_part, xd_var, str_sp_vars)
|
|
35
|
+
|
|
36
|
+
def _apply_2d(self, bd, list_eq, n_part, xd_var, str_sp_vars):
|
|
37
|
+
Nx, Ny = n_part[0], n_part[1]
|
|
38
|
+
hx = f"h{xd_var[0]}_"
|
|
39
|
+
hy = f"h{xd_var[0]}_"
|
|
40
|
+
result = [[] for _ in range(len(list_eq))]
|
|
41
|
+
|
|
42
|
+
if bd == "north":
|
|
43
|
+
for func in range(len(list_eq)):
|
|
44
|
+
for i in range(Nx):
|
|
45
|
+
expr = self._replace_xy(self.bd_func, f"{i} * {hx}", f"{Ny-1} * {hy}", str_sp_vars)
|
|
46
|
+
result[func].append(expr)
|
|
47
|
+
|
|
48
|
+
elif bd == "south":
|
|
49
|
+
for func in range(len(list_eq)):
|
|
50
|
+
for i in range(Nx):
|
|
51
|
+
expr = self._replace_xy(self.bd_func, f"{i} * {hx}", f"0 * {hy}", str_sp_vars)
|
|
52
|
+
result[func].append(expr)
|
|
53
|
+
|
|
54
|
+
elif bd == "east":
|
|
55
|
+
for func in range(len(list_eq)):
|
|
56
|
+
for j in range(Ny):
|
|
57
|
+
expr = self._replace_xy(self.bd_func, f"{Nx-1} * {hx}", f"{j} * {hy}", str_sp_vars)
|
|
58
|
+
result[func].append(expr)
|
|
59
|
+
|
|
60
|
+
elif bd == "west":
|
|
61
|
+
for func in range(len(list_eq)):
|
|
62
|
+
for j in range(Ny):
|
|
63
|
+
expr = self._replace_xy(self.bd_func, f"0 * {hx}", f"{j} * {hy}", str_sp_vars)
|
|
64
|
+
result[func].append(expr)
|
|
65
|
+
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
def _apply_1d(self, bd, list_eq, n_part, xd_var, str_sp_vars):
|
|
69
|
+
Nx = n_part[0]
|
|
70
|
+
hx = f"h{xd_var[0]}_"
|
|
71
|
+
result = [[] for _ in range(len(list_eq))]
|
|
72
|
+
|
|
73
|
+
if bd == "west":
|
|
74
|
+
for func in range(len(list_eq)):
|
|
75
|
+
expr = self._replace_xy(self.bd_func, f"0 * {hx}", "", str_sp_vars)
|
|
76
|
+
result[func].append(expr)
|
|
77
|
+
|
|
78
|
+
elif bd == "east":
|
|
79
|
+
for func in range(len(list_eq)):
|
|
80
|
+
expr = self._replace_xy(self.bd_func, f"{Nx-1} * {hx}", "", str_sp_vars)
|
|
81
|
+
result[func].append(expr)
|
|
82
|
+
|
|
83
|
+
return result
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .boundary_base import BoundaryCondition
|
|
3
|
+
|
|
4
|
+
class NeumannBC(BoundaryCondition):
|
|
5
|
+
|
|
6
|
+
def __init__(self, bd_func: str):
|
|
7
|
+
super().__init__(bd_func)
|
|
8
|
+
|
|
9
|
+
def apply(
|
|
10
|
+
self,
|
|
11
|
+
bd: str,
|
|
12
|
+
list_eq: List[List[str]],
|
|
13
|
+
n_part: List[int],
|
|
14
|
+
xd_var: List[str],
|
|
15
|
+
str_sp_vars: str = "",
|
|
16
|
+
) -> List[List[str]]:
|
|
17
|
+
|
|
18
|
+
is_2d = len(str_sp_vars) == 2
|
|
19
|
+
self._check_side(bd, is_2d)
|
|
20
|
+
bd = bd.lower()
|
|
21
|
+
|
|
22
|
+
if is_2d:
|
|
23
|
+
return self._apply_2d(bd, list_eq, n_part, xd_var, str_sp_vars)
|
|
24
|
+
return self._apply_1d(bd, list_eq, n_part, xd_var, str_sp_vars)
|
|
25
|
+
|
|
26
|
+
def _apply_2d(self, bd, list_eq, n_part, xd_var, str_sp_vars):
|
|
27
|
+
Nx, Ny = n_part[0], n_part[1]
|
|
28
|
+
n_funcs = len(list_eq)
|
|
29
|
+
result = [[] for _ in range(n_funcs)]
|
|
30
|
+
|
|
31
|
+
if bd in ("west", "east"):
|
|
32
|
+
length = Ny
|
|
33
|
+
elif bd in ("south", "north"):
|
|
34
|
+
length = Nx
|
|
35
|
+
else:
|
|
36
|
+
raise ValueError(f"Lado invalido: {bd}")
|
|
37
|
+
|
|
38
|
+
for func in range(n_funcs):
|
|
39
|
+
for _ in range(length):
|
|
40
|
+
result[func].append("0")
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
def _apply_1d(self, bd, list_eq, n_part, xd_var, str_sp_vars):
|
|
44
|
+
n_funcs = len(list_eq)
|
|
45
|
+
result = [[] for _ in range(n_funcs)]
|
|
46
|
+
for func in range(n_funcs):
|
|
47
|
+
result[func].append("0")
|
|
48
|
+
return result
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .boundary_base import BoundaryCondition
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PeriodicBC(BoundaryCondition):
|
|
6
|
+
def apply(
|
|
7
|
+
self,
|
|
8
|
+
bd: str,
|
|
9
|
+
list_eq: List[List[str]],
|
|
10
|
+
n_part: List[int],
|
|
11
|
+
xd_var: List[str],
|
|
12
|
+
str_sp_vars: str = "",
|
|
13
|
+
) -> List[List[str]]:
|
|
14
|
+
|
|
15
|
+
is_2d = len(str_sp_vars) == 2
|
|
16
|
+
self._check_side(bd, is_2d)
|
|
17
|
+
bd = bd.lower()
|
|
18
|
+
|
|
19
|
+
result = [[] for _ in range(len(list_eq))]
|
|
20
|
+
|
|
21
|
+
if not is_2d:
|
|
22
|
+
for func in range(len(list_eq)):
|
|
23
|
+
if bd == "west":
|
|
24
|
+
result[func].append(list_eq[func][-1])
|
|
25
|
+
elif bd == "east":
|
|
26
|
+
result[func].append(list_eq[func][0])
|
|
27
|
+
else:
|
|
28
|
+
raise NotImplementedError("Periódico 2D ainda não implementado.")
|
|
29
|
+
|
|
30
|
+
return result
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .boundary_base import BoundaryCondition
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RobinBC(BoundaryCondition):
|
|
6
|
+
|
|
7
|
+
def __init__(self, bd_func: str, alpha: str = "0", beta: str = "1"):
|
|
8
|
+
super().__init__(bd_func)
|
|
9
|
+
self.alpha = alpha
|
|
10
|
+
self.beta = beta
|
|
11
|
+
|
|
12
|
+
def apply(
|
|
13
|
+
self,
|
|
14
|
+
bd: str,
|
|
15
|
+
list_eq: List[List[str]],
|
|
16
|
+
n_part: List[int],
|
|
17
|
+
xd_var: List[str],
|
|
18
|
+
str_sp_vars: str = "",
|
|
19
|
+
) -> List[List[str]]:
|
|
20
|
+
|
|
21
|
+
is_2d = len(str_sp_vars) == 2
|
|
22
|
+
self._check_side(bd, is_2d)
|
|
23
|
+
bd = bd.lower()
|
|
24
|
+
|
|
25
|
+
if is_2d:
|
|
26
|
+
return self._apply_2d(bd, list_eq, n_part, xd_var, str_sp_vars)
|
|
27
|
+
return self._apply_1d(bd, list_eq, n_part, xd_var, str_sp_vars)
|
|
28
|
+
|
|
29
|
+
def _apply_2d(self, bd, list_eq, n_part, xd_var, str_sp_vars):
|
|
30
|
+
Nx, Ny = n_part[0], n_part[1]
|
|
31
|
+
n_funcs = len(list_eq)
|
|
32
|
+
result = [[] for _ in range(n_funcs)]
|
|
33
|
+
|
|
34
|
+
if bd in ("west", "east"):
|
|
35
|
+
length = Ny
|
|
36
|
+
elif bd in ("south", "north"):
|
|
37
|
+
length = Nx
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError(f"Lado invalido: {bd}")
|
|
40
|
+
|
|
41
|
+
for func in range(n_funcs):
|
|
42
|
+
for _ in range(length):
|
|
43
|
+
result[func].append("0")
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
def _apply_1d(self, bd, list_eq, n_part, xd_var, str_sp_vars):
|
|
47
|
+
n_funcs = len(list_eq)
|
|
48
|
+
result = [[] for _ in range(n_funcs)]
|
|
49
|
+
for func in range(n_funcs):
|
|
50
|
+
result[func].append("0")
|
|
51
|
+
return result
|
pdesolver/PDE.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class PDE:
|
|
2
|
+
|
|
3
|
+
eq = ''
|
|
4
|
+
func = ''
|
|
5
|
+
expr_ic = ''
|
|
6
|
+
sp_var = []
|
|
7
|
+
ivar = []
|
|
8
|
+
ivar_boundary = []
|
|
9
|
+
ic = []
|
|
10
|
+
|
|
11
|
+
def __init__(self, eq, func, sp_var, ivar, ivar_boundary, expr_ic,
|
|
12
|
+
west_bd="Dirichlet", west_func_bd="0",
|
|
13
|
+
east_bd="Dirichlet", east_func_bd="0",
|
|
14
|
+
north_bd="Dirichlet", north_func_bd="0",
|
|
15
|
+
south_bd="Dirichlet", south_func_bd="0"):
|
|
16
|
+
self.eq = eq
|
|
17
|
+
self.func = func
|
|
18
|
+
self.expr_ic = expr_ic
|
|
19
|
+
self.sp_var = sp_var
|
|
20
|
+
self.ivar = ivar
|
|
21
|
+
self.ivar_boundary = ivar_boundary
|
|
22
|
+
self.west_bd = west_bd
|
|
23
|
+
self.west_func_bd = west_func_bd
|
|
24
|
+
self.east_bd = east_bd
|
|
25
|
+
self.east_func_bd = east_func_bd
|
|
26
|
+
self.north_bd = north_bd
|
|
27
|
+
self.north_func_bd = north_func_bd
|
|
28
|
+
self.south_bd = south_bd
|
|
29
|
+
self.south_func_bd = south_func_bd
|
pdesolver/PDES.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from .Disc.Disc import df
|
|
2
|
+
from .Solvers.CN import cn
|
|
3
|
+
from .Solvers import RKF as SERKF45
|
|
4
|
+
from .Solvers.bdf2 import bdf2
|
|
5
|
+
from .Auxs.Visualize import visualize as _visualize
|
|
6
|
+
import sympy as sp
|
|
7
|
+
import numpy as np
|
|
8
|
+
import matplotlib
|
|
9
|
+
from .PDE import PDE
|
|
10
|
+
import json
|
|
11
|
+
from .Auxs.PDESEncoder import PDESEncoder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PDES:
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def disc_n(self):
|
|
18
|
+
return self._disc_n
|
|
19
|
+
|
|
20
|
+
@disc_n.setter
|
|
21
|
+
def disc_n(self, value):
|
|
22
|
+
self._disc_n = value
|
|
23
|
+
self.ic = self._ic_calc(self.pdes)
|
|
24
|
+
self.disc_results = None
|
|
25
|
+
self.dirichlet_constraints = {}
|
|
26
|
+
self.neumann_constraints = {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def __init__(self, pdes, disc_n, n_sp=1, n_temp=1):
|
|
30
|
+
self.pdes = pdes
|
|
31
|
+
self.eqs = [pde.eq for pde in pdes]
|
|
32
|
+
self.ivars = pdes[0].ivar
|
|
33
|
+
self.disc_results = None
|
|
34
|
+
self.dirichlet_constraints = {}
|
|
35
|
+
self.neumann_constraints = {}
|
|
36
|
+
|
|
37
|
+
self.funcs = [pde.func for pde in pdes]
|
|
38
|
+
self.sp_vars = pdes[0].sp_var
|
|
39
|
+
self._disc_n = disc_n
|
|
40
|
+
self.ic = self._ic_calc(pdes)
|
|
41
|
+
self.results = None
|
|
42
|
+
|
|
43
|
+
def _ic_calc(self, pdes):
|
|
44
|
+
all_ics = []
|
|
45
|
+
for pde in pdes:
|
|
46
|
+
expr = sp.parse_expr(pde.expr_ic)
|
|
47
|
+
sp_symbols = [sp.Symbol(v) for v in pde.sp_var]
|
|
48
|
+
grids = []
|
|
49
|
+
for i in range(len(pde.sp_var)):
|
|
50
|
+
a, b = pde.ivar_boundary[i]
|
|
51
|
+
grids.append(np.linspace(a, b, self.disc_n[i]))
|
|
52
|
+
mesh = np.meshgrid(*grids, indexing='ij')
|
|
53
|
+
f_ic = sp.lambdify(sp_symbols, expr, modules='numpy')
|
|
54
|
+
ic_values = f_ic(*mesh)
|
|
55
|
+
if np.isscalar(ic_values):
|
|
56
|
+
ic_values = np.broadcast_to(ic_values, mesh[0].shape)
|
|
57
|
+
all_ics.extend(ic_values.flatten().tolist())
|
|
58
|
+
return all_ics
|
|
59
|
+
|
|
60
|
+
def xs(self, vars):
|
|
61
|
+
nvars = vars.copy()
|
|
62
|
+
for i in range(len(nvars)):
|
|
63
|
+
nvars[i] = f'XX{i}'
|
|
64
|
+
return nvars
|
|
65
|
+
|
|
66
|
+
def discretize(self, method='central'):
|
|
67
|
+
flat_list, d_vars, dirichlet_constraints, neumann_constraints = df(
|
|
68
|
+
self, self.disc_n,
|
|
69
|
+
method=method,
|
|
70
|
+
west_bd = [pde.west_bd for pde in self.pdes],
|
|
71
|
+
west_func_bd = [pde.west_func_bd for pde in self.pdes],
|
|
72
|
+
east_bd = [pde.east_bd for pde in self.pdes],
|
|
73
|
+
east_func_bd = [pde.east_func_bd for pde in self.pdes],
|
|
74
|
+
north_bd = [pde.north_bd for pde in self.pdes],
|
|
75
|
+
north_func_bd = [pde.north_func_bd for pde in self.pdes],
|
|
76
|
+
south_bd = [pde.south_bd for pde in self.pdes],
|
|
77
|
+
south_func_bd = [pde.south_func_bd for pde in self.pdes],
|
|
78
|
+
)
|
|
79
|
+
self.disc_results = (flat_list, d_vars)
|
|
80
|
+
self.dirichlet_constraints = dirichlet_constraints
|
|
81
|
+
self.neumann_constraints = neumann_constraints
|
|
82
|
+
|
|
83
|
+
def solve(self, method='bdf2', tf=1.0, nt=100, tol=1e-6, verbose=False, **kwargs):
|
|
84
|
+
dt = tf / nt
|
|
85
|
+
dc = self.dirichlet_constraints
|
|
86
|
+
nc = getattr(self, 'neumann_constraints', {})
|
|
87
|
+
|
|
88
|
+
if method == 'bdf2':
|
|
89
|
+
self.results = bdf2(
|
|
90
|
+
self.disc_results[0], self.disc_results[1],
|
|
91
|
+
tf=tf, nt=nt, ic=self.ic,
|
|
92
|
+
n_funcs=len(self.funcs),
|
|
93
|
+
dirichlet_constraints=dc,
|
|
94
|
+
neumann_constraints=nc,
|
|
95
|
+
verbose=verbose,
|
|
96
|
+
**kwargs
|
|
97
|
+
)
|
|
98
|
+
elif method == 'CN':
|
|
99
|
+
self.results = cn(
|
|
100
|
+
self.disc_results[0], self.disc_results[1],
|
|
101
|
+
tf=tf, nt=nt, ic=self.ic,
|
|
102
|
+
n_funcs=len(self.funcs),
|
|
103
|
+
dirichlet_constraints=dc,
|
|
104
|
+
neumann_constraints=nc,
|
|
105
|
+
verbose=verbose,
|
|
106
|
+
**kwargs
|
|
107
|
+
)
|
|
108
|
+
elif method == 'RKF':
|
|
109
|
+
self.results = SERKF45.SERKF45_cuda(
|
|
110
|
+
self.disc_results[0],
|
|
111
|
+
ivar=self.ivars,
|
|
112
|
+
funcs=self.disc_results[1],
|
|
113
|
+
yn=self.ic,
|
|
114
|
+
sp_vars=self.sp_vars,
|
|
115
|
+
n=100,
|
|
116
|
+
n_funcs=len(self.funcs),
|
|
117
|
+
dt_init=dt,
|
|
118
|
+
tol=tol,
|
|
119
|
+
x0=0,
|
|
120
|
+
xn=nt * dt,
|
|
121
|
+
dirichlet_constraints=dc,
|
|
122
|
+
neumann_constraints=nc,
|
|
123
|
+
verbose=verbose
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"Metodo '{method}' desconhecido. Use: 'bdf2', 'CN' ou 'RKF'."
|
|
128
|
+
)
|
|
129
|
+
return self.results
|
|
130
|
+
|
|
131
|
+
def visualize(self, mode='heatmap', func_idx=0, time_step=-1, **kwargs):
|
|
132
|
+
_visualize(self, mode=mode, func_idx=func_idx,
|
|
133
|
+
time_step=time_step, **kwargs)
|
|
134
|
+
|
|
135
|
+
def __repr__(self):
|
|
136
|
+
status = 'resolvido' if self.results is not None else 'nao resolvido'
|
|
137
|
+
return (f"PDES(funcs={self.funcs}, disc_n={self.disc_n}, "
|
|
138
|
+
f"sp_vars={self.sp_vars}, status='{status}')")
|
|
139
|
+
|
|
140
|
+
def save_to_json(self, filepath="pdes1.json"):
|
|
141
|
+
data = {
|
|
142
|
+
'disc_n': list(self.disc_n),
|
|
143
|
+
'pdes': [pde.__dict__ for pde in self.pdes],
|
|
144
|
+
'results': self.results,
|
|
145
|
+
}
|
|
146
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
147
|
+
json.dump(data, f, cls=PDESEncoder, indent=4, ensure_ascii=False)
|
|
148
|
+
print(f"Objeto salvo com sucesso em: {filepath}")
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def load_from_json(cls, filepath, pde_class=PDE):
|
|
152
|
+
import inspect
|
|
153
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
154
|
+
data = json.load(f)
|
|
155
|
+
|
|
156
|
+
init_params = set(inspect.signature(pde_class.__init__).parameters) - {'self'}
|
|
157
|
+
|
|
158
|
+
reconstructed_pdes = []
|
|
159
|
+
for pde_dict in data['pdes']:
|
|
160
|
+
kwargs = {k: v for k, v in pde_dict.items() if k in init_params}
|
|
161
|
+
reconstructed_pdes.append(pde_class(**kwargs))
|
|
162
|
+
|
|
163
|
+
obj = cls(pdes=reconstructed_pdes, disc_n=data['disc_n'])
|
|
164
|
+
obj.disc_results = None
|
|
165
|
+
obj.dirichlet_constraints = {}
|
|
166
|
+
obj.neumann_constraints = {}
|
|
167
|
+
|
|
168
|
+
if data.get('results') is not None:
|
|
169
|
+
raw = data['results']
|
|
170
|
+
try:
|
|
171
|
+
obj.results = np.array(raw)
|
|
172
|
+
except ValueError:
|
|
173
|
+
obj.results = np.array([np.array(r) for r in raw], dtype=object)
|
|
174
|
+
|
|
175
|
+
return obj
|
pdesolver/Solvers/CN.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from tabnanny import verbose
|
|
2
|
+
import time
|
|
3
|
+
import numpy as np
|
|
4
|
+
import sympy as sp
|
|
5
|
+
from sympy.parsing.sympy_parser import parse_expr
|
|
6
|
+
import scipy.sparse as sp_sparse
|
|
7
|
+
from scipy.sparse.linalg import spsolve
|
|
8
|
+
|
|
9
|
+
from .solver_base import (
|
|
10
|
+
compile_equations, extract_linear_structure, detect_linearity,
|
|
11
|
+
eval_F, picard_step, newton_step,
|
|
12
|
+
make_history, save_to_history,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _make_bc_lambda(expr_str: str):
|
|
17
|
+
t_sym, x_sym, y_sym = sp.symbols('t x y')
|
|
18
|
+
expr = parse_expr(expr_str)
|
|
19
|
+
return sp.lambdify((t_sym, x_sym, y_sym), expr, modules='numpy')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cn(flat_list, d_vars, tf, nt, ic, n_funcs=None,
|
|
23
|
+
nonlinear_method='newton', tol_nl=1e-8, max_iter_nl=20,
|
|
24
|
+
dirichlet_constraints=None,
|
|
25
|
+
neumann_constraints=None,
|
|
26
|
+
verbose=False):
|
|
27
|
+
dt = tf / nt
|
|
28
|
+
n = len(d_vars)
|
|
29
|
+
u = np.array(ic, dtype=np.float64).flatten()
|
|
30
|
+
|
|
31
|
+
dirichlet_constraints = dirichlet_constraints or {}
|
|
32
|
+
neumann_constraints = neumann_constraints or {}
|
|
33
|
+
|
|
34
|
+
dirichlet_lambdas = {
|
|
35
|
+
idx: _make_bc_lambda(info['expr'])
|
|
36
|
+
for idx, info in dirichlet_constraints.items()
|
|
37
|
+
}
|
|
38
|
+
neumann_lambdas = {
|
|
39
|
+
idx: _make_bc_lambda(info['expr'])
|
|
40
|
+
for idx, info in neumann_constraints.items()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
h_neumann = None
|
|
44
|
+
if neumann_constraints:
|
|
45
|
+
all_x = set(); all_y = set()
|
|
46
|
+
for info in dirichlet_constraints.values():
|
|
47
|
+
all_x.add(round(info['x'], 12))
|
|
48
|
+
all_y.add(round(info['y'], 12))
|
|
49
|
+
for info in neumann_constraints.values():
|
|
50
|
+
all_x.add(round(info['x'], 12))
|
|
51
|
+
all_y.add(round(info['y'], 12))
|
|
52
|
+
Nx_est = len(all_x); Ny_est = len(all_y)
|
|
53
|
+
if Nx_est >= 2:
|
|
54
|
+
h_neumann = 1.0 / (Nx_est - 1)
|
|
55
|
+
elif Ny_est >= 2:
|
|
56
|
+
h_neumann = 1.0 / (Ny_est - 1)
|
|
57
|
+
else:
|
|
58
|
+
raise RuntimeError(
|
|
59
|
+
"Nao foi possivel inferir o passo h da malha para Neumann."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _apply_dirichlet(u, t_val):
|
|
63
|
+
for idx, info in dirichlet_constraints.items():
|
|
64
|
+
f = dirichlet_lambdas[idx]
|
|
65
|
+
u[idx] = float(f(t_val, info['x'], info['y']))
|
|
66
|
+
return u
|
|
67
|
+
|
|
68
|
+
def _apply_neumann(u, t_val):
|
|
69
|
+
if h_neumann is None:
|
|
70
|
+
return u
|
|
71
|
+
two_h = 2.0 * h_neumann
|
|
72
|
+
for idx, info in neumann_constraints.items():
|
|
73
|
+
f = neumann_lambdas[idx]
|
|
74
|
+
f_val = float(f(t_val, info['x'], info['y']))
|
|
75
|
+
u[idx] = (4.0 * u[info['idx_n1']]
|
|
76
|
+
- u[info['idx_n2']]
|
|
77
|
+
+ two_h * f_val) / 3.0
|
|
78
|
+
return u
|
|
79
|
+
|
|
80
|
+
def _apply_bcs(u, t_val):
|
|
81
|
+
u = _apply_dirichlet(u, t_val)
|
|
82
|
+
u = _apply_neumann(u, t_val)
|
|
83
|
+
return u
|
|
84
|
+
|
|
85
|
+
u = _apply_bcs(u, 0.0)
|
|
86
|
+
|
|
87
|
+
final_list, use_groups, n_elements = make_history(n_funcs, n)
|
|
88
|
+
|
|
89
|
+
funcs = compile_equations(flat_list, d_vars)
|
|
90
|
+
|
|
91
|
+
overwrite_indices = list(dirichlet_constraints.keys()) + list(neumann_constraints.keys())
|
|
92
|
+
is_linear, L = detect_linearity(funcs, n,
|
|
93
|
+
dirichlet_indices=overwrite_indices)
|
|
94
|
+
|
|
95
|
+
I = sp_sparse.eye(n, format='csr')
|
|
96
|
+
|
|
97
|
+
if is_linear:
|
|
98
|
+
_, fonte_func = extract_linear_structure(funcs, n, verbose=False)
|
|
99
|
+
A_impl = I - (dt / 2.0) * L
|
|
100
|
+
A_expl = I + (dt / 2.0) * L
|
|
101
|
+
else:
|
|
102
|
+
if verbose:
|
|
103
|
+
print(f" [CN] EDP nao-linear detectada - usando {nonlinear_method.upper()} "
|
|
104
|
+
f"(tol={tol_nl:.0e}, max_iter={max_iter_nl})")
|
|
105
|
+
_, fonte_func = extract_linear_structure(funcs, n, verbose=False)
|
|
106
|
+
|
|
107
|
+
save_to_history(u, final_list, use_groups, n_funcs, n_elements)
|
|
108
|
+
|
|
109
|
+
t0 = time.time()
|
|
110
|
+
total_iters = 0
|
|
111
|
+
|
|
112
|
+
for passo in range(nt):
|
|
113
|
+
tempo_atual = passo * dt
|
|
114
|
+
tempo_proximo = (passo + 1) * dt
|
|
115
|
+
|
|
116
|
+
if is_linear:
|
|
117
|
+
f_n = fonte_func(tempo_atual)
|
|
118
|
+
f_n1 = fonte_func(tempo_proximo)
|
|
119
|
+
rhs = A_expl.dot(u) + (dt / 2.0) * (f_n + f_n1)
|
|
120
|
+
u = spsolve(A_impl, rhs)
|
|
121
|
+
u = _apply_bcs(u, tempo_proximo)
|
|
122
|
+
|
|
123
|
+
else:
|
|
124
|
+
F_n = eval_F(funcs, tempo_atual, u)
|
|
125
|
+
rhs_hist = u + (dt / 2.0) * F_n
|
|
126
|
+
|
|
127
|
+
if nonlinear_method == 'newton':
|
|
128
|
+
u, n_iter = newton_step(
|
|
129
|
+
funcs, u, tempo_proximo, dt, n, rhs_hist,
|
|
130
|
+
alpha=0.5, max_iter=max_iter_nl,
|
|
131
|
+
tol_nl=tol_nl, verbose=verbose
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
u, n_iter = picard_step(
|
|
135
|
+
funcs, u, tempo_proximo, dt, n, rhs_hist,
|
|
136
|
+
alpha=0.5, max_iter=max_iter_nl,
|
|
137
|
+
tol_nl=tol_nl, verbose=verbose
|
|
138
|
+
)
|
|
139
|
+
u = _apply_bcs(u, tempo_proximo)
|
|
140
|
+
total_iters += n_iter
|
|
141
|
+
|
|
142
|
+
save_to_history(u, final_list, use_groups, n_funcs, n_elements)
|
|
143
|
+
|
|
144
|
+
elapsed = time.time() - t0
|
|
145
|
+
if verbose:
|
|
146
|
+
print(f" [CN] Loop de tempo: {elapsed:.3f}s", end="")
|
|
147
|
+
if not is_linear:
|
|
148
|
+
print(f" | Media iteracoes/passo: {total_iters/nt:.1f}", end="")
|
|
149
|
+
print()
|
|
150
|
+
|
|
151
|
+
return u, final_list
|